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 :-)
Related
I have 2 forms: Form A and Form B. I also have a property field class.
Form A contains the label I want changed when a property is changed. Form B contains code that will change the property field.
Property Class Code:
public class Controller
{
private static string _customerID;
public static string customerID
{
get { return _customerID; }
set
{
_customerID = value;
if (_customerID != "")
{
FormA.ChangeMe();
}
}
}
}
Form B Code:
private void something_Click(object sender, SomethingEventArgs e) {
Controller.customerID = "Cool";
}
Form A Code:
public static void ChangeMe()
{
var frmA = new FormA();
MessageBox.Show("Test: " + Controller.customerID); //This works! Shows Cool
frmA.lb2Change.Text = Controller.customerID; //This kind of works..
MessageBox.Show("Test2: " + frmA.lb2Change.Text); //This shows the correct value. Shows Cool
}
The property field value is passed (which I know from the MessageBox) however it does not update the value on the form label itself. Why is this? What am I doing wrong? I also believe there is a better alternative for achieving what ChangeMe() method is intended to achieve -- if so are there any suggestions?
You can do the following
To define a delegate
To Implement Property Change Notification
Delegate
public delegate void OnCustomerIDChanging(object sender,CancelEventArgs e);
public delegate void OnCustomerIDChanged(object sender,object value);
public class Controller
{
private static string _customerID;
public event OnCustomerIDChanging CustoerIDChanging;
public event OnCustomerIDChanged CustoerIDChanged;
public static string customerID
{
get { return _customerID; }
set
{
// make sure that the value has a `value` and different from `_customerID`
if(!string.IsNullOrEmpty(value) && _customerID!=value)
{
if(CustomerIDChanging!=null)
{
var state = new CancelEventArgs();
// raise the event before changing and your code might reject the changes maybe due to violation of validation rule or something else
CustomerIDChanging(this,state);
// check if the code was not cancelled by the event from the from A
if(!state.Cancel)
{
// change the value and raise the event Changed
_customerID = value;
if(CustomerIDChanged!=null)
CustomerIDChanged(this,value);
}
}
}
}
}
}
in your Form and when you are initiating the Controller Object
var controller = new Controller();
controller.CustomerIDChanging +=(sd,args) =>{
// here you can test if you want really to change the value or not
// in case you want to reject the changes you can apply
args.Cancel = true;
};
controller.CustomerIDChanged +=(sd,args) =>{
// here you implement the code **Changed already**
}
The above code will give you a great control over your code, also will make your controller code reusable and clean. Same
result you can get by implementing INotifyPropertyChanged interface
INotifyPropertyChanged
you might have a look on this article to get more information
In your static method ChangeMe you are creating a new Form every time, you want to Change the value. Instead of that you want to change the value of an existing form. Therefor your Controller needs an instance of this FormA. Try it like this:
public class Controller
{
//You can pass the form throught the constructor,
//create it in constructor, ...
private FormA frmA;
private string _customerID;
public string customerID
{
get { return _customerID; }
set
{
_customerID = value;
if (_customerID != "")
{
frmA.ChangeMe();
}
}
}
}
Now you donĀ“t need to be static in your FormA:
public void ChangeMe()
{
MessageBox.Show("Test: " + Controller.customerID);
this.lb2Change.Text = Controller.customerID;
}
I want to create a simple input dialog. so I added a Windows From to my application and in that window I've put a label, a textbox and a button; For now it's easy to fill the required proprieties of my controls with the predefined values using constructors and return the entered value with result;
public partial class GeneralInputForm : Form
{
public string result = "";
public GeneralInputForm(string label, string defaultValue = "")
{
InitializeComponent();
label1.Text = label;
textBox1.Text = defaultValue;
}
private void button1_Click(object sender, EventArgs e)
{
result = textBox1.Text;
Close();
}
}
And now creating an instance of the from and filling the parameters with the required values;
GeneralInputForm formInput = new GeneralInputForm("Please enter your age:");
formInput.ShowDialog();
//formInput.result holds the value
My questions raises here; What if I want to check for the validity of the value that the user has entered in the textbox in the GeneralInputForm itself (and not later when instantiating the form with checking the result field)? I mean is there such a functionality in C# (delegates maybe) so that the user himself can define a method and pass a it as one of the parameters of the constructor which that method checks the textbox1.text value to be sure that for example it's successfully can be parsed into an int or float or string?
I don't want to add an ugly string parameter called variableType to my constructor and ask user to say if he requires a 'int' or 'float' or 'string' or whatever and then I myself by checking the value of variableType, wrote different statements for making sure that the entered text can be successfully parsed into int or float or string (it would be an ugly and limited solution)
Change GeneralInputForm to GeneralInputForm<T>. Is a generic class, just like List<String>.
You can use two field Func one to cast the Text to the object you want and one to check the return just like this:
public partial class GeneralInputForm<T> : Form
{
public T result = default(T);
Func<T, Boolean> check = (input) => true; // Default check
Func<String, T> cast = null; // Default cast
public GeneralInputForm(string label, string defaultValue = "", Func<String, T> cast, Func<T, Boolean> check)
{
InitializeComponent();
label1.Text = label;
textBox1.Text = defaultValue;
if(check != null)
this.check = check;
this.cast = cast;
}
private void button1_Click(object sender, EventArgs e)
{
if(cast != null)
{
T casted = cast(textBox1.Text);
if(casted != null && check(casted)){
result = textBox1.Text;
Close();
}
}
}
}
And you can use it like this:
Func<String, int> cast = (input) => int.Parse(input);
Func<int, Boolean> check = (input) => input > 0;
GeneralInputForm<int> form = new GeneralInputForm<int>("Enter a number:", "1", cast, check);
form.ShowDialog();
//etc....
More info about Generics.
This way of doing thing is named Dependecy Injection, and this approach is the constructor injection type. More info.
You don't need to pass it as a parameter. The validation function can be overridden using inheritance.
public partial class GeneralInputForm : Form
{
// Returns true if this text is valid for a textbox
protected virtual bool IsValid(string text)
{
return true; // default behaviour
}
private void button1_Click(object sender, EventArgs e)
{
if (IsValid(textBox1.Text))
{
result = textBox1.Text; // ok
Close();
}
else
{
// Show error message
}
}
}
Now you can create a form which custom-validates textboxes, by overriding (ie. replacing) the IsValid() function:
public partial class SpecificInputForm : GeneralInputForm
{
// Blank textboxes are not valid, and are restricted to 31 characters
protected override bool IsValid(string text)
{
if (string.IsNullOrWhitespace(text))
return false;
return (text.Length < 32);
}
}
While #Oscar's answer would work, making forms generic is a bit of a bad idea because the Winforms designer has aversion to them (try to inherit from that form and you'll see).
I'd probably make it so that it's not the form that is generic, but a method:
public partial class GeneralInputForm : Form
{
private GeneralInputForm() // Private constructor
{
InitializeComponent();
}
public static bool TryGetValue<T>(
string label,
string defaultValue,
Func<T, bool> check,
out T result
) where T : IConvertible
{
result = default(T);
using (var frm = new GeneralInputForm())
{
frm.label1.Text = label;
frm.textBox1.Text = defaultValue;
frm.Closing += (o, e) =>
{
var myForm = (GeneralInputForm) o;
// User closed not clicking the button?
if (myForm.DialogResult != DialogResult.OK)
return;
try
{
var checkval = (T) Convert.ChangeType(myForm.textBox1.Text, typeof (T));
if (!check(checkval))
e.Cancel = true;
}
catch
{
e.Cancel = true;
}
};
if (frm.ShowDialog() == DialogResult.OK)
{
result = (T)Convert.ChangeType(frm.textBox1.Text, typeof (T));
return true;
}
}
return false;
}
private void button1_Click(object sender, EventArgs e)
{
// Don't use `Close()` on modal forms, it's always better to
// return a dialog result. This will close the form aswell
DialogResult = DialogResult.OK;
}
}
Then use it like:
int result;
if(GeneralInputForm.TryGetValue("Enter an integer over 10",
"10",
(x) => x > 10,
out result))
MessageBox.Show(result.ToString());
else
/* User closed the form by other means */
Or:
string result;
if (GeneralInputForm.TryGetValue("Enter \"Hello\"",
"Goodbye",
(x) => x == "Hello",
out result))
This will not let you close the form by clicking button1 unless the value is correct, but will let you close it by other means (alt+f4, clicking the x, etc.). It'll return true if it was closed by the button, or false if it wasn't (you can put a Cancel button there).
You could also make the defaultValue parameter be of type T and assign defaultValue.ToString() to the textbox
A messagebox can be shown to the user in the Closing delegate if the value is not valid, or you can make your own logic
This requires that T is IConvertible, but about any type having a Parse method is
Thanks to everyone for the answers; I ended up to this simple and clear solution using delegates;
public partial class SimpleInputForm : Form
{
public string Value = "";
public delegate bool VerifyDel(string input);
private VerifyDel Methods;
public SimpleInputForm(string lable, VerifyDel method)
{
InitializeComponent();
label1.Text = lable;
Methods = new VerifyDel(method);
}
private void button1_Click(object sender, EventArgs e)
{
if (Methods(textBox1.Text))
{
Value = textBox1.Text;
Close();
}
}
}
and now I show the form with user defined functionality for the evaluation of input;
SimpleInputForm simpleInput = new SimpleInputForm("Enter your age:", (input => {
try
{
int age = int.Parse(input);
return true;
}
catch
{
MessageBox.Show("Invalid value for age!");
return false;
}
}));
//simpleInput.Value holds the input value
I have this code on each form in my app that has textboxes to prevent the textboxes that are ReadOnly from being tabbed to:
private void FrmInventory_Load(object sender, EventArgs e)
{
foreach (var txtbx in Controls.OfType<TextBox>())
{
txtbx.TabStop = (!txtbx.ReadOnly);
}
}
It would be good to only have this code in one place, but how can I do that, as each time that "external" method was called, it would touch TextBoxes on the calling form, which smells a little fishy. Is an extension method the way to go, something like:
public static bool TextboxIsReadOnly(this TextBox txtbx)
{
return txtbx.ReadOnly;
}
...and then call it like this:
foreach (var txtbx in Controls.OfType<TextBox>())
{
txtbx.TabStop = TextboxIsReadOnly(txtbx);
}
?
That doesn't seem like it's of much value - I still would have to put most of the code in each form, just as things stand now. Creating a custom textbox that is both ReadOnly and TabStop = false seems a little overkillish...
Is there a way to have this logic execute for every TextBox-containing form, without reproducing the code all throughout the project?
Having a base class that performs that step and making it the base for all your forms would work, although you would need to be careful about calling the base version of the overloaded methods.
You can create a baseForm and Inherit that form in each of your forms.
Add a new Windows Form to your project(baseForm) and create load event
public class baseForm: Form
{
public baseForm()
{
this.Load += baseForm_Load;
}
void baseForm_Load(object sender, EventArgs e)
{
var t = GetAll<TextBoxX>(this);
foreach (var txtbx in Controls.OfType<TextBox>())
{
txtbx.TabStop = (!txtbx.ReadOnly);
}
}
public static List<T> GetAll<T>(Form f1)
{
List<T> f = new List<T>();
try {
if (f1 != null) {
CheckInner<T>(f1.Controls, ref f);
}
} catch (Exception ex) {
f.Clear();
}
return f;
}
}
And finally in each form you can do like this
public partial class FrmInventory : baseForm
{
}
Just to elaborate on the Extension Method solution you hinted at.
Extension Method
public static partial class MyExtensions
{
public static void UpdateTabStop(this TextBox txtBox)
{
txtBox.TabStop = !(txtBox.ReadOnly);
}
public static void UpdateTabStop(this Form frm)
{
foreach (var txtBox in frm.Controls.OfType<TextBox>())
{
txtBox.UpdateTabStop();
}
}
}
Then, on any Form you would do this.UpdateTabStop()... you should of course do this in an event after the controls are initialized, like Load.
I am looking for a way to trim all user input in ASP.NET without calling Trim() on every string instance. I came across extending the DefaultModelBinder for MVC. Is there a way to do this in web forms? What options are available? As a less desirable option, is there a way to incorporate this into the set method of a class?
You could create a custom TextBox which always returns a trimmed version of the text:
public class CustomTextBox : TextBox
{
public override string Text
{
get { return base.Text.Trim(); }
set { base.Text = value; }
}
}
Then just use this instead of the normal TextBox anywhere you need this behavior.
Here is the utility method to trim all TextBoxes in a page (or a parent control) recursively.
public static void TrimTextBoxesRecursive(Control root)
{
foreach (Control control in root.Controls)
{
if (control is TextBox)
{
var textbox = control as TextBox;
textbox.Text = textbox.Text.Trim();
}
else
{
TrimTextBoxesRecursive(control);
}
}
}
Usage
protected void Button1_Click(object sender, EventArgs e)
{
TrimTextBoxesRecursive(Page);
}
You have to call this extension method from the appropriate parent, e.g. Page.TrimTextControls
public static void TrimTextControls(this Control parent, bool TrimLeading)
{
foreach (TextBox txt in parent.GetAllControls().OfType<TextBox>())
{
if (TrimLeading)
{
txt.Text = txt.Text.Trim();
}
else
{
txt.Text = txt.Text.TrimEnd();
}
}
}
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
}
}
}