validating controls in tabs winforms - c#

Im implementing application which has main window and in this second window which has a lot of tabs in tabcontrol.
On each tab I have a lot of control which values may be edited by user, some of them has to be filled, some need to has values between x and y and so on.
Main windows has got save button. Point is that if in any tab control isnt validated then saving shouldnt be possible and appropriate tab should be opened and validation shown. Could you please tell me any advice of how to create such mechanism ? Maybe any generic methods ?
thanks for help

Try this link
WinForms TabControl validation: Switch to a tab where validation failed
Dictionary<TabPage, HashSet<Control>> _tabControls
= new Dictionary<TabPage, HashSet<Control>>();
public OptionsForm()
{
InitializeComponent();
RegisterToValidationEvents();
}
private void RegisterToValidationEvents()
{
foreach (TabPage tab in this.OptionTabs.TabPages)
{
var tabControlList = new HashSet<Control>();
_tabControls[tab] = tabControlList;
foreach (Control control in tab.Controls)
{
var capturedControl = control; //this is necessary
control.Validating += (sender, e) =>
tabControlList.Add(capturedControl);
control.Validated += (sender, e) =>
tabControlList.Remove(capturedControl);
}
}
}
private void Ok_Button_Click(object sender, EventArgs e)
{
if (this.ValidateChildren())
{
_settings.Save();
this.Close();
}
else
{
var unvalidatedTabs = _tabControls.Where(kvp => kvp.Value.Count != 0)
.Select(kvp => kvp.Key);
TabPage firstUnvalidated = unvalidatedTabs.FirstOrDefault();
if (firstUnvalidated != null &&
!unvalidatedTabs.Contains(OptionTabs.SelectedTab))
OptionTabs.SelectedTab = firstUnvalidated;
}
}

Related

C# Errorprovider.ContainerControl.Controls contains just TabControl

I have a Form that contains a TabControl and an ErrorProvider. One of the tabs has several textboxes and a button. The textboxes use the Validating event to SetError() if contents are not valid. When the button is pressed, this runs:
bool ok = true;
foreach (Control c in errorProviderDv.ContainerControl.Controls)
{
MessageBox.Show(c.Name);
if (errorProviderDv.GetError(c) != "")
{
ok = false;
}
}
The TabControl is the only control in errorProviderDv.ContainerControl.Controls, even though several errors are set and are displaying in the form.
Am I doing something wrong? Does the ErrorProvider need to be a child of the tab instead of the form?
The TabControl itself is a container which contains TabPages. Those TabPages are containers which contain your textboxes.
The following code will get you want you want but you may want to clean it up to make a recursive call passing in a container so it will work for almost any type.
private void button1_Click(object sender, EventArgs e)
{
bool ok = true;
foreach (Control c in errorProviderDv.ContainerControl.Controls)
{
if (c is TabControl)
{
foreach (Control t in (c as TabControl).SelectedTab.Controls)
{
MessageBox.Show(t.Name);
if (errorProviderDv.GetError(t) != "")
{
ok = false;
}
}
}
}
}

refresh form after button click

I'm getting the first element of a table articles where column(statusArticle=false).The problem is that i want to refresh the form after the button click so i can interact with next element, but the form is not refreshed, i tried several codes exept the one of application.restart that is heavy! How can i refresh the form at the boutton click without restarting application?
private void button_Click(object sender, EventArgs e)
{
using (DbEntities db = new DbEntities())
{
Articles firstArticle = db.Articles.FirstOrDefault(u => u.statusArticle == false);
if (firstArticle != null)
{
firstArticle.statusArticle = true;
db.SaveChanges();
MessageBox.Show("Article validated", "OK");
this.Refresh();
}
}
}
Within your class create the following:
private void ShowArticle(Article article)
{
/* The code currently in your constructor for displaying the
first article goes here */
}
For your constructor:
public MyForm()
{
using(DbEntities db = new DbEntities())
{
Articles firstArticle = db.Articles.FirstOrDefault(u => u.statusArticle == false);
if( firstArticle != null ) ShowArticle( firstArticle );
}
}
In your button click handler instead of calling Refresh simple replace it with a call to ShowArticle passing in "firstArticle". The code above could be cleaned up a bit, but it should do.

Preventing multiple repeat selection of synchronized Controls?

The working code sample here synchronizes (single) selection in a TreeView, ListView, and ComboBox via the use of lambda expressions in a dictionary where the Key in the dictionary is a Control, and the Value of each Key is an Action<int>.
Where I am stuck is that I am getting multiple repetitions of execution of the code that sets the selection in the various controls in a way that's unexpected : it's not recursing : there's no StackOverFlow error happening; but, I would like to figure out why the current strategy for preventing multiple selection of the same controls is not working.
Perhaps the real problem here is distinguishing between a selection update triggered by the end-user and a selection update triggered by the code that synchronizes the other controls ?
Note: I've been experimenting with using Delegates, and forms of Delegates like Action<T>, to insert executable code in Dictionaries : I "learn best" by posing programming "challenges" to myself, and implementing them, as well as studying, at the same time, the "golden words" of luminaries like Skeet, McDonald, Liberty, Troelsen, Sells, Richter.
Note: Appended to this question/code, for "deep background," is a statement of how I used to do things in pre C#3.0 days where it seemed like I did need to use explicit measures to prevent recursion when synchronizing selection.
Code : Assume a WinForms standard TreeView, ListView, ComboBox, all with the same identical set of entries (i.e., the TreeView has only root nodes; the ListView, in Details View, has one Column).
private Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();
private void Form1_Load(object sender, EventArgs e)
{
// add the Controls to be synchronized to the Dictionary
// with appropriate Action<int> lambda expressions
ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));
// optionally install event handlers at run-time like so :
// treeView1.AfterSelect += (object obj, TreeViewEventArgs evt)
// => { synchronizeSelection(evt.Node.Index, treeView1); };
// listView1.SelectedIndexChanged += (object obj, EventArgs evt)
// => { if (listView1.SelectedIndices.Count > 0)
// { synchronizeSelection(listView1.SelectedIndices[0], listView1);} };
// comboBox1.SelectedValueChanged += (object obj, EventArgs evt)
// => { synchronizeSelection(comboBox1.SelectedIndex, comboBox1); };
}
private void synchronizeSelection(int i, Control currentControl)
{
foreach(Control theControl in ControlToAction.Keys)
{
// skip the 'current control'
if (theControl == currentControl) continue;
// for debugging only
Console.WriteLine(theControl.Name + " synchronized");
// execute the Action<int> associated with the Control
ControlToAction[theControl](i);
}
}
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
synchronizeSelection(e.Node.Index, treeView1);
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
// weed out ListView SelectedIndexChanged firing
// with SelectedIndices having a Count of #0
if (listView1.SelectedIndices.Count > 0)
{
synchronizeSelection(listView1.SelectedIndices[0], listView1);
}
}
private void comboBox1_SelectedValueChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex > -1)
{
synchronizeSelection(comboBox1.SelectedIndex, comboBox1);
}
}
background : pre C# 3.0
Seems like, back in pre C# 3.0 days, I was always using a boolean flag to prevent recursion when multiple controls were updated. For example, I'd typically have code like this for synchronizing a TreeView and ListView : assuming each Item in the ListView was synchronized with a root-level node of the TreeView via a common index :
// assume ListView is in 'Details View,' has a single column,
// MultiSelect = false
// FullRowSelect = true
// HideSelection = false;
// assume TreeView
// HideSelection = false
// FullRowSelect = true
// form scoped variable
private bool dontRecurse = false;
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
if(dontRecurse) return;
dontRecurse = true;
listView1.Items[e.Node.Index].Selected = true;
dontRecurse = false;
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if(dontRecurse) return
// weed out ListView SelectedIndexChanged firing
// with SelectedIndices having a Count of #0
if (listView1.SelectedIndices.Count > 0)
{
dontRecurse = true;
treeView1.SelectedNode = treeView1.Nodes[listView1.SelectedIndices[0]];
dontRecurse = false;
}
}
Then it seems, somewhere around FrameWork 3~3.5, I could get rid of the code to suppress recursion, and there was was no recursion (at least not when synchronizing a TreeView and a ListView). By that time it had become a "habit" to use a boolean flag to prevent recursion, and that may have had to do with using a certain third party control.
I believe your approach is totally fine. If you want something a little more advanced, see Rein in runaway events with the "Latch", which allows for
void TabControl_TabSelected(object sender, TabEventArgs args)
{
_latch.RunLatchedOperation(
delegate
{
ContentTab tab = (ContentTab)TabControl.SelectedTab;
activatePresenter(tab.Presenter, tab);
});
}
Note: I always assumed an SO user should never answer their own question. But, after reading-up on SO-Meta on this issue, I find it's actually encouraged. Personally, I would never vote on my own answer as "accepted."
This "new solution" uses a strategy based on distinguishing between a control being updated as a result of end-user action, and a control being updated by synchronizing code: this issue was mentioned, as a kind of "rhetorical question," in the original question.
I consider this an improvement: it works; it prevents multiple update calls; but, I also "suspect" it's still "not optimal": appended to this code example is a list of "suspicions."
// VS Studio 2010 RC 1, tested under Framework 4.0, 3.5
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace SynchronizationTest_3
{
public partial class Form1 : Form
{
private readonly Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();
// new code : keep a reference to the control the end-user clicked
private Control ClickedControl;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));
// new code : screen out redundant calls generated by other controls
// being updated
treeView1.AfterSelect += (obj, evt)
=>
{
if (treeView1 == ClickedControl) SynchronizeSelection(evt.Node.Index);
};
listView1.SelectedIndexChanged += (obj, evt)
=>
{
if (listView1.SelectedIndices.Count > 0 && listView1 == ClickedControl)
{
SynchronizeSelection(listView1.SelectedIndices[0]);
}
};
comboBox1.SelectedValueChanged += (obj, evt)
=>
{
if (comboBox1 == ClickedControl) SynchronizeSelection(comboBox1.SelectedIndex);
};
// new code here : all three controls share a common MouseDownHandler
treeView1.MouseDown += SynchronizationMouseDown;
listView1.MouseDown += SynchronizationMouseDown;
comboBox1.MouseDown += SynchronizationMouseDown;
// trigger the first synchronization
ClickedControl = treeView1;
SynchronizeSelection(0);
}
// get a reference to the control the end-user moused down on
private void SynchronizationMouseDown(object sender, MouseEventArgs e)
{
ClickedControl = sender as Control;
}
// revised code using state of ClickedControl as a filter
private void SynchronizeSelection(int i)
{
// we're done if the reference to the clicked control is null
if (ClickedControl == null) return;
foreach (Control theControl in ControlToAction.Keys)
{
if (theControl == ClickedControl) continue;
// for debugging only
Console.WriteLine(theControl.Name + " synchronized");
ControlToAction[theControl](i);
}
// set the clicked control to null
ClickedControl = null;
}
}
}
Why I "suspect" this is not optimal:
the idiosyncratic behavior of WinForms controls has to be taken into account: for example, the ListView Control fires its Selected### Events before it fires a Click Event: ComboBox and TreeView fire their Click Events before their SelectedValueChanged and AfterSelect Events respectively: so had to experiment to find that using 'MouseDown would work the same across all three controls.
a "gut level" feeling that I've gone "too far" out on "some kind of limb" here: a sense a much simpler solution might be possible.

When the user clicks a button, I want all other buttons to be disabled

I have a winforms app and the main (and only) form has several buttons. When the user clicks a button, I want all other buttons to be disabled.
I know I could do this the long way by setting "Enabled" to false on all buttons but the one which has been clicked in the clicked event handler of that button, but this is a long and tedious approach.
I got the collection of controls on the form, but this does not include the several buttons I have:
System.Windows.Forms.Control.ControlCollection ctrls = this.Controls;
How can I go about implementing this functionality in a mantainable fashion?
Thanks
DisableControls(Control c)
{
c.Enable = false;
foreach(Control child in c.Controls)
DisableControls(child)
}
private void Form1_Load(object sender, EventArgs e)
{
foreach (Control ctrl in this.Controls)
{
if (ctrl is Button)
{
Button btn = (Button)ctrl;
btn.Click += ButtonClick;
}
}
}
private void ButtonClick(object sender, EventArgs e)
{
foreach (Control ctrl in this.Controls)
{
if (ctrl is Button)
{
Button btn = (Button)ctrl;
if (btn != (Button)sender)
{
btn.Enabled = false;
}
}
}
}
You could use databinding, so you only iterate once on startup:
public partial class Form1 : Form
{
public bool ButtonsEnabled { get; set; }
public Form1()
{
InitializeComponent();
// Enable by default
ButtonsEnabled = true;
// Set the bindings.
FindButtons(this);
}
private void button_Click(object sender, EventArgs e)
{
// Set the bound property
ButtonsEnabled = false;
// Force the buttons to update themselves
this.BindingContext[this].ResumeBinding();
// Renable the clicked button
Button thisButton = sender as Button;
thisButton.Enabled = true;
}
private void FindButtons(Control control)
{
// If the control is a button, bind the Enabled property to ButtonsEnabled
if (control is Button)
{
control.DataBindings.Add("Enabled", this, "ButtonsEnabled");
}
// Check it's children
foreach(Control child in control.Controls)
{
FindButtons(child);
}
}
}
Expanding on Alex Reitbort's answer, here's an example method I just wrote 20 minutes ago for this project I'm doing:
private void Foo(bool enabled)
{
foreach (Control c in this.Controls)
if (c is Button && c.Tag != null)
c.Enabled = enabled;
}
This function is not recursive like his is. However, because I know there are no Buttons within a container other than my form so I don't need to worry about that. If there were child controls (ie controls within a GroupBox for example), I'd probably modify that code to be something like this;
private void ToggleControls()
{
Foo(this.Controls, false) // this being the form, of course.
}
private void Foo(Control.ControlCollection controls, bool enabled)
{
foreach (Control c in controls)
{
if (c is Button && c.Tag != null)
c.Enabled = enabled;
if (c.HasChildren)
Foo(c.Controls, enabled);
}
}
I actually like his method a little more, that style of recursion looks a little cleaner than mine I think. Just showing you an alternate way.
Note; I have 6 buttons on my form and I only want 4 of them to have their Enabled property modified by this method. To accomplish this, I set their Tag property to some random string. That is why I check for c.Tag in my if statements. You can remove that if you know you want to disable every control.

Handling a Click for all controls on a Form

I have a .NET UserControl (FFX 3.5). This control contains several child Controls - a Panel, a couple Labels, a couple TextBoxes, and yet another custom Control. I want to handle a right click anywhere on the base Control - so a right click on any child control (or child of a child in the case of the Panel). I'd like to do it so that it's maintainable if someone makes changes to the Control without having to wire in handlers for new Controls for example.
First I tried overriding the WndProc, but as I suspected, I only get messages for clicks on the Form directly, not any of its children. As a semi-hack, I added the following after InitializeComponent:
foreach (Control c in this.Controls)
{
c.MouseClick += new MouseEventHandler(
delegate(object sender, MouseEventArgs e)
{
// handle the click here
});
}
This now gets clicks for controls that support the event, but Labels, for example, still don't get anything. Is there a simple way to do this that I'm overlooking?
If the labels are in a subcontrol then you'd have to do this recursively:
void initControlsRecursive(ControlCollection coll)
{
foreach (Control c in coll)
{
c.MouseClick += (sender, e) => {/* handle the click here */});
initControlsRecursive(c.Controls);
}
}
/* ... */
initControlsRecursive(Form.Controls);
To handle a MouseClick event for right click on all the controls on a custom UserControl:
public class MyClass : UserControl
{
public MyClass()
{
InitializeComponent();
MouseClick += ControlOnMouseClick;
if (HasChildren)
AddOnMouseClickHandlerRecursive(Controls);
}
private void AddOnMouseClickHandlerRecursive(IEnumerable controls)
{
foreach (Control control in controls)
{
control.MouseClick += ControlOnMouseClick;
if (control.HasChildren)
AddOnMouseClickHandlerRecursive(control.Controls);
}
}
private void ControlOnMouseClick(object sender, MouseEventArgs args)
{
if (args.Button != MouseButtons.Right)
return;
var contextMenu = new ContextMenu(new[] { new MenuItem("Copy", OnCopyClick) });
contextMenu.Show((Control)sender, new Point(args.X, args.Y));
}
private void OnCopyClick(object sender, EventArgs eventArgs)
{
MessageBox.Show("Copy menu item was clicked.");
}
}

Categories