Does anyone have a piece of code to make all Controls (or even all TextBoxes) in a Form which is read-only at once without having to set every Control to read-only individually?
Write an extension method which gathers controls and child controls of specified type:
public static IEnumerable<T> GetChildControls<T>(this Control control) where T : Control
{
var children = control.Controls.OfType<T>();
return children.SelectMany(c => GetChildControls<T>(c)).Concat(children);
}
Gather TextBoxes on the form (use TextBoxBase to affect RichTextBox, etc - #Timwi's solution):
IEnumerable<TextBoxBase> textBoxes = this.GetChildControls<TextBoxBase>();
Iterate thru collection and set read-only:
private void AreTextBoxesReadOnly(IEnumerable<TextBoxBase> textBoxes, bool value)
{
foreach (TextBoxBase tb in textBoxes) tb.ReadOnly = value;
}
If want - use caching - #igor's solution
In Form:
if (_cached == null)
{
_cached = new List<TextBox>();
foreach(var control in Controls)
{
TextBox textEdit = control as TextBox;
if (textEdit != null)
{
textEdit.ReadOnly = false;
_cached.Add(textEdit);
}
}
}
else
{
foreach(var control in _cached)
{
control .ReadOnly = false;
}
}
Add recursion also (Controls can be placed into other controls (panels)).
You should be able to write yourself a utility function to do this. You can iterate over the form’s controls and then each control’s child controls recursively. For example:
public static void SetEnableOnAllControls(Control parentControl, bool enable)
{
parentControl.Enabled = enable;
foreach (Control control in parentControl.Controls)
SetEnableOnAllControls(control, enable);
}
[...]
// inside your form:
SetEnableOnAllControls(this, false);
This doesn’t take care of ToolStrips, which aren’t controls. You could write a separate, similar method for those.
Notice that the above disables the form itself too. If you don’t want that, try this:
public static void SetEnableOnAllChildControls(Control parentControl, bool enable)
{
foreach (Control control in parentControl.Controls)
{
control.Enabled = enable;
SetEnableOnAllChildControls(control, enable);
}
}
If you really meant the ReadOnly property, which is only relevant for TextBoxes, try this:
public static void SetReadOnlyOnAllControls(Control parentControl, bool readOnly)
{
if (parentControl is TextBoxBase)
((TextBoxBase) parentControl).ReadOnly = readOnly;
foreach (Control control in parentControl.Controls)
SetReadOnlyOnAllControls(control, readOnly);
}
I would use reflection to check to see if the generic Control object has an Enabled property.
private static void DisableControl(Control control)
{
PropertyInfo enProp = control.GetType().GetProperty("Enabled");
if (enProp != null)
{
enProp.SetValue(control, false, null);
}
foreach (Control ctrl in control.Controls)
{
DisableControl(ctrl);
}
}
this.Enabled = false;
Depends on what you doing really, you might want to consider putting the control within a panel and disabling that.
I haven't tested this, but it should work:
foreach (var textBox in this.Controls.OfType<TextBox>())
textBox.ReadOnly = true;
Edit: This is not such a good solution it seems: see Timwi's comment.
I just developed a recursive solution that handles any kind of Web Control, using a simple static method and ASP.NET polymorphysm.
/// <summary>
/// Handle "Enabled" property of a set of Controls (and all of the included child controls through recursivity)
/// By default it disable all, making all read-only, but it can also be uset to re-enable everything, using the "enable" parameter
/// </summary>
/// <param name="controls">Set of controls to be handled. It could be the entire Page.Controls set or all of the childs of a given control (property "Controls" of any Control-derived class)</param>
/// <param name="enable">Desired value of the property "enable" of each control. </param>
public static void DisableControls(ControlCollection controls, bool enable = false)
{
foreach (Control control in controls)
{
var wCtrl = control as WebControl;
if (wCtrl != null)
{
wCtrl.Enabled = enable;
}
if (control.Controls.Count > 0)
DisableControls(control.Controls, enable);
}
}
/// <summary>
/// Enable a set of Controls (and all of the included child controls through recursivity).
/// Friendly name for DisableControls(controls, true), that achieve the same result.
/// </summary>
/// <param name="Controls">Set of controls to be handled. It could be the entire Page.Controls set or all of the childs of a given control (property "Controls" of any Control-derived class)</param>
public static void EnableControls(ControlCollection controls)
{
DisableControls(controls, true);
}
This is tested and looks fairly fast (less than a millisecond in a web form with 25+ controls to be disabled).
If you prefer an extension method, i think it should be enough to change the solution as follows:
public static void DisableControls(this Control control, bool enable = false)
{
foreach (Control ctrl in control.Controls)
{
var wCtrl = ctrl as WebControl;
if (wCtrl != null)
{
wCtrl.Enabled = enable;
}
if (ctrl.Controls.Count > 0)
ctrl.DisableControls(enable);
}
}
public static void EnableControls(this Control control)
{
control.DisableControls(true);
}
Related
I have a user control that I load into a MainWindow at runtime. I cannot get a handle on the containing window from the UserControl.
I have tried this.Parent, but it's always null. Does anyone know how to get a handle to the containing window from a user control in WPF?
Here is how the control is loaded:
private void XMLLogViewer_MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem application = sender as MenuItem;
string parameter = application.CommandParameter as string;
string controlName = parameter;
if (uxPanel.Children.Count == 0)
{
System.Runtime.Remoting.ObjectHandle instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, controlName);
UserControl control = instance.Unwrap() as UserControl;
this.LoadControl(control);
}
}
private void LoadControl(UserControl control)
{
if (uxPanel.Children.Count > 0)
{
foreach (UIElement ctrl in uxPanel.Children)
{
if (ctrl.GetType() != control.GetType())
{
this.SetControl(control);
}
}
}
else
{
this.SetControl(control);
}
}
private void SetControl(UserControl control)
{
control.Width = uxPanel.Width;
control.Height = uxPanel.Height;
uxPanel.Children.Add(control);
}
Try using the following:
Window parentWindow = Window.GetWindow(userControlReference);
The GetWindow method will walk the VisualTree for you and locate the window that is hosting your control.
You should run this code after the control has loaded (and not in the Window constructor) to prevent the GetWindow method from returning null. E.g. wire up an event:
this.Loaded += new RoutedEventHandler(UserControl_Loaded);
I'll add my experience. Although using the Loaded event can do the job, I think it may be more suitable to override the OnInitialized method. Loaded occurs after the window is first displayed. OnInitialized gives you chance to make any changes, for example, add controls to the window before it is rendered.
Use VisualTreeHelper.GetParent or the recursive function below to find the parent window.
public static Window FindParentWindow(DependencyObject child)
{
DependencyObject parent= VisualTreeHelper.GetParent(child);
//CHeck if this is the end of the tree
if (parent == null) return null;
Window parentWindow = parent as Window;
if (parentWindow != null)
{
return parentWindow;
}
else
{
//use recursion until it reaches a Window
return FindParentWindow(parent);
}
}
I needed to use the Window.GetWindow(this) method within Loaded event handler. In other words, I used both Ian Oakes' answer in combination with Alex's answer to get a user control's parent.
public MainView()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainView_Loaded);
}
void MainView_Loaded(object sender, RoutedEventArgs e)
{
Window parentWindow = Window.GetWindow(this);
...
}
If you are finding this question and the VisualTreeHelper isn't working for you or working sporadically, you may need to include LogicalTreeHelper in your algorithm.
Here is what I am using:
public static T TryFindParent<T>(DependencyObject current) where T : class
{
DependencyObject parent = VisualTreeHelper.GetParent(current);
if( parent == null )
parent = LogicalTreeHelper.GetParent(current);
if( parent == null )
return null;
if( parent is T )
return parent as T;
else
return TryFindParent<T>(parent);
}
This approach worked for me but it is not as specific as your question:
App.Current.MainWindow
How about this:
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
public static class ExVisualTreeHelper
{
/// <summary>
/// Finds the visual parent.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sender">The sender.</param>
/// <returns></returns>
public static T FindVisualParent<T>(DependencyObject sender) where T : DependencyObject
{
if (sender == null)
{
return (null);
}
else if (VisualTreeHelper.GetParent(sender) is T)
{
return (VisualTreeHelper.GetParent(sender) as T);
}
else
{
DependencyObject parent = VisualTreeHelper.GetParent(sender);
return (FindVisualParent<T>(parent));
}
}
}
I've found that the parent of a UserControl is always null in the constructor, but in any event handlers the parent is set correctly. I guess it must have something to do with the way the control tree is loaded. So to get around this you can just get the parent in the controls Loaded event.
For an example checkout this question WPF User Control's DataContext is Null
Another way:
var main = App.Current.MainWindow as MainWindow;
It's working for me:
DependencyObject GetTopLevelControl(DependencyObject control)
{
DependencyObject tmp = control;
DependencyObject parent = null;
while((tmp = VisualTreeHelper.GetParent(tmp)) != null)
{
parent = tmp;
}
return parent;
}
This didn't work for me, as it went too far up the tree, and got the absolute root window for the entire application:
Window parentWindow = Window.GetWindow(userControlReference);
However, this worked to get the immediate window:
DependencyObject parent = uiElement;
int avoidInfiniteLoop = 0;
while ((parent is Window)==false)
{
parent = VisualTreeHelper.GetParent(parent);
avoidInfiniteLoop++;
if (avoidInfiniteLoop == 1000)
{
// Something is wrong - we could not find the parent window.
break;
}
}
Window window = parent as Window;
window.DragMove();
If you just want to get a specific parent, not only the window, a specific parent in the tree structure, and also not using recursion, or hard break loop counters, you can use the following:
public static T FindParent<T>(DependencyObject current)
where T : class
{
var dependency = current;
while((dependency = VisualTreeHelper.GetParent(dependency) ?? LogicalTreeHelper.GetParent(dependency)) != null
&& !(dependency is T)) { }
return dependency as T;
}
Just don't put this call in a constructor (since the Parent property is not yet initialized). Add it in the loading event handler, or in other parts of your application.
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
DependencyObject GetTopParent(DependencyObject current)
{
while (VisualTreeHelper.GetParent(current) != null)
{
current = VisualTreeHelper.GetParent(current);
}
return current;
}
DependencyObject parent = GetTopParent(thisUserControl);
The Window.GetWindow(userControl) will return the actual window only after the window was initialized (InitializeComponent() method finished).
This means, that if your user control is initialized together with its window (for instance you put your user control into the window's xaml file), then on the user control's OnInitialized event you will not get the window (it will be null), cause in that case the user control's OnInitialized event fires before the window is initialized.
This also means that if your user control is initialized after its window, then you can get the window already in the user control's constructor.
Gold plated edition of the above (I need a generic function which can infer a Window within the context of a MarkupExtension:-
public sealed class MyExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider) =>
new MyWrapper(ResolveRootObject(serviceProvider));
object ResolveRootObject(IServiceProvider serviceProvider) =>
GetService<IRootObjectProvider>(serviceProvider).RootObject;
}
class MyWrapper
{
object _rootObject;
Window OwnerWindow() => WindowFromRootObject(_rootObject);
static Window WindowFromRootObject(object root) =>
(root as Window) ?? VisualParent<Window>((DependencyObject)root);
static T VisualParent<T>(DependencyObject node) where T : class
{
if (node == null)
throw new InvalidOperationException("Could not locate a parent " + typeof(T).Name);
var target = node as T;
if (target != null)
return target;
return VisualParent<T>(VisualTreeHelper.GetParent(node));
}
}
MyWrapper.Owner() will correctly infer a Window on the following basis:
the root Window by walking the visual tree (if used in the context of a UserControl)
the window within which it is used (if it is used in the context of a Window's markup)
Different approaches and different strategies. In my case I could not find the window of my dialog either through using VisualTreeHelper or extension methods from Telerik to find parent of given type. Instead, I found my my dialog view which accepts custom injection of contents using Application.Current.Windows.
public Window GetCurrentWindowOfType<TWindowType>(){
return Application.Current.Windows.OfType<TWindowType>().FirstOrDefault() as Window;
}
When I iterate over a bunch of different controls on a Form, instead of trying to access the Text property:
String text = String.Empty;
foreach(Control control in this.Controls)
{
try
{
text = control.Text;
}
catch(Exception exception)
{
// This control probably doesn't have the Text property.
Debug.WriteLine(exception.Message);
}
}
Is there a way to just determine whether or not a given control has a Text property? Something like this:
String text = String.Empty;
foreach(Control control in this.Controls)
{
if(control has Text property)
{
text = control.Text;
}
}
I absolutely despise the Try/Catch blocks (unless there is no better alternative, of-course).
All Control objects have a Text property, so there is no point in using reflection to determine that. It will always return true.
Your problem actually is that some controls throw an exception from their Text property because they don't support it.
If you also want to be able to use custom controls that you don't know in advance, you should stick to your current solution and catch the exceptions. However, you should catch the specific exception thrown, for example NotSupportedException.
If you only ever encounter controls that you know in advance, you can select the controls that you know have a working Text property. For example:
public static bool HasWorkingTextProperty(Control control)
{
return control is Label
|| control is TextBox
|| control is ComboBox;
}
var controlsWithText = from c in this.Controls
where HasWorkingTextProperty(c)
select c;
foreach(var control in controlsWithText)
{
string text = control.Text;
// Do something with it.
}
And if you implement your own custom controls that may or may not have a Text property, then you can derive them from a base class that indicates this:
public abstract class CustomControlBase : Control
{
public virtual bool HasText
{
get { return false; }
}
}
public class MyCustomControl : CustomControlBase
{
public override bool HasText
{
get { return true; }
}
public override string Text
{
get { /* Do something. */ }
set { /* Do something. */ }
}
}
public static bool HasWorkingTextProperty(Control control)
{
return (control is CustomControlBase && ((CustomControlBase)control).HasText)
|| control is Label
|| control is TextBox
|| control is ComboBox;
}
Your question is How to determine if Control has Text property, so here is how you can do it using Reflection:
control.GetType().GetProperties().Any(x => x.Name == "Text");
Edit: If you take a look at the Control class, you will see it has a Text property.
Now, if some custom control that overrides the Control class throws an exception when accessing to the Text property, it is violating the Liskov substitution principle. In that case, I suggest you identifying those controls, although what you're doing seems to be fine.
Check this out:
private void Form1_Load(object sender, EventArgs e)
{
foreach (Control ctrl in this.Controls)
{
PropertyInfo[] properties = ctrl.GetType().GetProperties();
foreach(PropertyInfo pi in properties)
if (pi.Name == "Text")
{
//has text
}
}
}
I have an asp.net dashboard site that allows a user to load HTML templates from a dropdownlist. There are multiple types of DevExpress components on the page, including the ASPxDockPanel. If a user changes templates I get an error that the dockpanel already exists, I would like to include a recursive function like the one below that checks to see if any ASPxDockPanels are present on the page, and if they are present remove them. This works for only the first dock panel then bombs out. I think this is because an enumerable set of controls cannot be modified while looping through it. How can I loop though the controls and remove the dock panels at runtime?
protected void LoadTableTemplate(string selectedTemplate, int currentMode)
{
FindAllDockPanels(this);
}
public void FindAllDockPanels(Control ctrl)
{
if (ctrl != null)
{
foreach (Control control in ctrl.Controls)
{
if (control is ASPxDockPanel)
{
ctrl.Controls.Remove(control);
control.Dispose();
}
FindAllDockPanels(control);
}
}
}
Use a temporary collection, like so:
public void FindAllDockPanels(Control ctrl) {
if (ctrl != null) {
List<Control> remove = new List<Control>();
foreach (Control control in ctrl.Controls) {
if (control is ASPxDockPanel) {
remove.Add( control );
}
}
foreach(Control control in remove) {
control.Controls.Remove( control );
control.Dispose(); // do you really need to dispose of them?
}
FindAllDockPanels(control);
}
}
If you find yourself doing this often, it might be worth moving these "DelayedDelete" actions to an extension method, like so:
public static void DelayedRemove<T>(this IEnumerable<T item> collection, T itemToRemove) {
// add it to a private static dictionary bound to the `collection` instance.
}
public static void DelayedRemoveFinish(this IEnumerable<T item> collection) {
// empty the private static dictionary in here
}
then you'd use it like so:
public void FindAllDockPanels(Control ctrl) {
if (ctrl != null) {
foreach (Control control in ctrl.Controls) {
if (control is ASPxDockPanel) control.Controls.DelayedRemove( control );
}
control.Controls.DelayedRemoveFinish();
FindAllDockPanels(control);
}
}
Much cleaner, no? :)
I want to know if any ErrorProvider are active in my form.
being able to find this out might help reduce my code..
I did find this thing here Counting ErrorProvider
but incase someone knows a better way... so here goes.
Ok so basically I have a WinForm which has many TextBoxes
Now when user enters values I use Validating to perform validation and if it does not match Regex I set the ErrorProvider ON for that Control.. similarly if the user changes the value to a acceptable one I switch ErrorProvider OFF for that Control..
but when SAVE is clicked i have to do another check anyways incase the user did not listen to me and change the thing like he was supposed to and still clicked SAVE.. I dont want the thing crashing..
soo mm is there like a thing where I could say if ErrorProviders is not active then proceed with save else message box saying change it.
[ANOTHER QUESTION]
Umm When Validating it only Validates when the Control loses Focus... I kinda of want it to do validation when user stops typing.. I hope you get what I mean
Like Email Address(textbox) when user is typing his/her name in I [DON'T] want it to do validation yet, but when user has finished entering is waiting for ErrorProvider to disappear(But it doesn't coz it only does that when control loses focus) 2 odd seconds after typing can i make the validation take place?
Unfortunately, the ErrorProvider control doesn't provide such functionality. You'd best go with the custom error provider classes from the link you posted.
Otherwise, you could create a method that you would call instead of SetError
int errorCount;
void SetError(Control c, string message)
{
if (message == "")
errorCount--;
else
errorCount++;
errorProvider.SetError(c, message);
}
Or you could make an extension method for the ErrorProvider class that would set the error and increment a counter or something along those lines.
And last but not least, you could iterate through all the controls. Slow, but it works:
bool IsValid()
{
foreach (Control c in errorProvider1.ContainerControl.Controls)
if (errorProvider1.GetError(c) != "")
return false;
return true;
}
Edit
I've written a quick extension class for the error provider:
public static class ErrorProviderExtensions
{
private static int count;
public static void SetErrorWithCount(this ErrorProvider ep, Control c, string message)
{
if (message == "")
{
if (ep.GetError(c) != "")
count--;
}
else
count++;
ep.SetError(c, message);
}
public static bool HasErrors(this ErrorProvider ep)
{
return count != 0;
}
public static int GetErrorCount(this ErrorProvider ep)
{
return count;
}
}
I haven't tested it extensively, so you might want to do a bit more validation before calling SetError on your ErrorProvider.
I know this is a bit older question and the extension is working except if someone try to SetErrorWithCount twice for the same object, the count is counted twice.
so, here I come with the update extension base on Netfangled extension
public static class ErrorProviderExtensions
{
private static int count;
public static void SetErrorWithCount(this ErrorProvider ep, Control c, string message)
{
if (message == "")
{
if (ep.GetError(c) != "")
count--;
}
else
if (ep.GetError(c) == "")
count++;
ep.SetError(c, message);
}
public static bool HasErrors(this ErrorProvider ep)
{
return count != 0;
}
public static int GetErrorCount(this ErrorProvider ep)
{
return count;
}
}
OK let me use easier method:
currently you are using implicit validation approach... to immediately validate the control.
I think you want to check if all the controls in the form is validated before do some actions, so just check that all the child control is validated. by using The explicit validation approach
in the validating event for each control you can use:-
Private Sub ProductIDTextBox_Validating(sender As System.Object, e As System.ComponentModel.CancelEventArgs) Handles ProductIDTextBox.Validating
If ProductIDTextBox.Text = "" Then
ErrorProvider1.SetError(ProductIDTextBox, "you have to enter text")
e.Cancel = True
Return
End If
ErrorProvider1.SetError(ProductIDTextBox, "")
End Sub
then you can check for all the controls by :-
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
If ValidateChildren() Then
MessageBox.Show("Validation succeeded!")
Else
MessageBox.Show("Validation failed.")
End If
End Sub
hope this will help since i spend hours to find the proper method
It seems like a logical thing to have but unfortunately it's not provided natively.
You could extend the ErrorProvider as other mentioned, or simply iterate all the controls under it and look for an error, something like
bool IsValidationError(ErrorProvider errorProvider, Control.ControlCollection controlCollection)
{
foreach(Control child in controlCollection)
{
// The child or one of its children has an error.
if (!errorProvider.GetError(child).IsNullOrEmpty() || IsValidationError(errorProvider, child.Controls))
return true;
}
return false;
}
and you'd call IsValidationError(errorProvider, errorProvider.ContainerControl.Controls), or passing a more limited control collection.
Obviously you'd want to avoid iterating tons of controls, but that simple solution should be fine in lots of cases. Also even if you do have tons of controls, you'd probably group them together using Panel, TabControl, GroupBox, ... so you could easily avoid iterating absolutely all the controls.
Note: this is similar to one of the possibilities described in https://stackoverflow.com/a/12327212/276648 except it looks for both null and empty, and it iterates possible grand children recursively.
I have multiple Control elements (TextBoxes) attached to their corresponding ErrorProviders.
I was trying to find a way to countAllErrors(), or even better, to handleEachError(),
so that's what I came up with:
In the Class:
internal TextBox email_textbox;
internal TextBox city_textbox;
internal TextBox address_textbox;
internal TextBox phone_textbox;
internal TextBox lastName_textbox;
internal TextBox firstName_textbox;
private ErrorProvider firstName_errPro;
private ErrorProvider lastName_errPro;
private ErrorProvider phone_errPro;
private ErrorProvider address_errPro;
private ErrorProvider city_errPro;
private ErrorProvider email_errPro;
internal Dictionary<ErrorProvider, Control> errors;
In the Form's Constructor:
errors = new Dictionary<ErrorProvider, Control>(6);
errors.Add( firstName_errPro ,firstName_textbox );
errors.Add( lastName_errPro ,lastName_textbox );
errors.Add( phone_errPro ,phone_textbox );
errors.Add( address_errPro ,address_textbox );
errors.Add( city_errPro ,city_textbox );
errors.Add( email_errPro ,email_textbox );
Counting all errors:
int countAllErrors()
{
int numOfErrors = errors.Count<KeyValuePair<ErrorProvider, Control>>(ep => ep.Key.GetError(ep.Value) != "");
return numOfErrors;
}
Handling each error:
void handleEachError()
{
foreach (KeyValuePair<ErrorProvider, Control> errPair in errors.Where(ep => ep.Key.GetError(ep.Value) != ""))
{
ErrorProvider errorProvider = errPair.Key;
Control control = errPair.Value;
string errorStr = errorProvider.GetError(control);
// handle the error:
// for example - show it's text in a MessageBox:
MessageBox.Show(errorStr);
}
}
lemme know if it was helpful.. ;)
You can also simply create an inherited class.
public class TrackedErrorProvider : ErrorProvider
{
public TrackedErrorProvider() : base() { }
public TrackedErrorProvider(ContainerControl parentControl) : base(parentControl) { }
public TrackedErrorProvider(IContainer container) : base(container) { }
public int ErrorsCount { get; protected set; } = 0;
public bool HasErrors
{
get { return ErrorsCount > 0; }
}
public new void SetError(Control control, string message)
{
//Check if there is already an error linked to the control
bool errorExistsForControl = !string.IsNullOrEmpty(GetError(control));
//If removing error from the control
if (string.IsNullOrEmpty(message))
{
/* Decreases the counter only if:
* - an error already existed for the control
* - the counter is not 0
*/
if (errorExistsForControl && ErrorsCount > 0) ErrorsCount--;
}
else //If setting error message to the control
{
//Increments the error counter only if an error wasn't set for the control (otherwise it is just replacing the error message)
if (!errorExistsForControl) ErrorsCount++;
}
base.SetError(control, message);
}
public void RemoveError(Control control)
{
SetError(control, null);
}
}
Some answers here are extremely error prone, because they share a static count variable in the extension method. No!
My extension methods use my Nuget package Overby.Extensions.Attachments to store the associated controls along with the ErrorProvider so that the # of errors can be counted.
using System.Collections.Generic;
using System.Windows.Forms;
using System.Linq;
using Overby.Extensions.Attachments; // PM> Install-Package Overby.Extensions.Attachments
namespace MyApp
{
public static class ErrorProviderExtensions
{
public static void TrackControl(this ErrorProvider ep, Control c)
{
var controls = ep.GetOrSetAttached(() => new HashSet<Control>()).Value;
controls.Add(c);
}
public static void SetErrorWithTracking(this ErrorProvider ep, Control c, string error)
{
ep.TrackControl(c);
ep.SetError(c, error);
}
public static int GetErrorCount(this ErrorProvider ep)
{
var controls = ep.GetOrSetAttached(() => new HashSet<Control>()).Value;
var errControls = from c in controls
let err = ep.GetError(c)
let hasErr = !string.IsNullOrEmpty(err)
where hasErr
select c;
var errCount = errControls.Count();
return errCount;
}
public static void ClearError(this ErrorProvider ep, Control c)
{
ep.SetError(c, null);
}
}
}
My way is with extension methods so I can simply call errorProvider.Valid(). The ErrorProvider actually has a reference to its main control(the form) if implemented correctly, so it should work on all forms with a single instance. ValidateChildren() doesn't seem to return a useful value.
This is what I use:
public static bool Valid(this ErrorProvider ep)
{
ep.ContainerControl.ValidateChildren();
return ep.ChildrenAreValid(ep.ContainerControl);
}
private static bool ChildrenAreValid(this ErrorProvider ep, Control control)
{
if (!string.IsNullOrWhiteSpace(ep.GetError(control))) return false;
foreach (Control c in control.Controls)
if (!(ep.ChildrenAreValid(c))) return false;
return true;
}
Typically I have a method to enable/disable a save button or something as such:
private bool VerifyIntegrity() => (btnSave.Enabled = errorProvider.Valid());
Which is run at input events.
In my case, I didn't use a static class but an instance of error counter.
public class ErrorCounter
{
private List<string> _propertiesError = new List<string>();
private static ObjectIDGenerator _IDGenerator = new ObjectIDGenerator();
public bool HasErrors
{
get => ErrorCount != 0;
}
public int ErrorCount
{
get => _propertiesError.Count;
}
/// <summary>
/// Record object validation rule state.
/// </summary>
/// <param name="sender">"this" object reference must be passed into parameter each time SetError is called</param>
/// <param name="message"></param>
/// <param name="property"></param>
/// <returns></returns>
public string SetError(object sender, string property, string message)
{
string propertyUniqueID = GetPropertyUniqueID(sender, property);
if (string.IsNullOrWhiteSpace(message))
{
if (_propertiesError.Exists(x => x == propertyUniqueID))
{
_propertiesError.Remove(propertyUniqueID);
}
}
else
{
if (!_propertiesError.Exists(x => x == propertyUniqueID))
{
_propertiesError.Add(propertyUniqueID);
}
}
return message;
}
private string GetPropertyUniqueID(object sender, string property)
{
bool dummyFirstTime;
return property + "_" + _IDGenerator.GetId(sender, out dummyFirstTime);
}
}
Usage :
Declare in your main ViewModel
public class MainViewModel : ViewModelBase, IDataErrorInfo
...
private ErrorCounter _errorCounter = new ErrorCounter();
...
// Entry validation rules
public string Error => string.Empty;
public string this[string columnName]
{
get
{
switch (columnName)
{
case nameof(myProperty_1):
if (string.IsNullOrWhiteSpace(myProperty_1))
return _errorCounter.SetError(this, columnName, "Error 1");
break;
case nameof(myProperty_2):
if (string.IsNullOrWhiteSpace(myProperty_2))
return _errorCounter.SetError(this, columnName, "Error 2");
break;
default:
break;
}
return _errorCounter.SetError(this, columnName, string.Empty);
}
}
ObjectIDGenerator combined with the propertie name allows to count only once each properties.
If you need to use the same instance of _errorCounter in an object collection member of another class, pass it to the constructor of the other class.
and that's all :-)
There is a way to get who lost his focus in a c# form without using the LostFocus event each component?
[edit]
I need for a On Screen Keyboard.
I need to store last focussed control to fire keypress, but i need to do it to all in the window.
Also the main project is wpf, than i have some component nested as itemsTemplate and so on...
I finally used this:
foreach (Control uie in FindInLogicalTreeDown(this, typeof(TextBox))) AssignEvents(uie);
private static IEnumerable<DependencyObject> FindInLogicalTreeDown(DependencyObject obj, Type type)
{
if (obj != null)
{
if (obj.GetType() == type) { yield return obj; }
foreach (object child in LogicalTreeHelper.GetChildren(obj))
if (typeof(DependencyObject).IsAssignableFrom(child.GetType()))
foreach (var nobj in FindInLogicalTreeDown((DependencyObject)child, type)) yield return nobj;
}
yield break;
}
void AssignEvents(Control element)
{
element.GotMouseCapture += new MouseEventHandler(Component_GotFocus);
}
public Control LastFocus { get; set; }
public void Component_GotFocus(object sender, RoutedEventArgs e)
{
LastFocus = (Control)sender;
if (LastFocus.GetType() == typeof(TextBox)) { KeyboardVisible = true; }
}
i don't think there is any way until unless you subscribe events and keep track which lost focus event has fired last