How to determine if Control has Text property - c#

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
}
}
}

Related

How to create custom TextBox control?

I want to perform Trim() method on each TexBox control on my page, before value is returned. I dont' want to hard-code the same code for each TexBox control, I want to do it in more elegant way.
I've found made the following class
namespace System.Web.UI.WebControls
{
public partial class TrimmedTextBuox : TextBox
{
private string text;
public override string Text
{
get { return string.IsNullOrEmpty(text) ? text : text.Trim(); }
set { text = value; }
}
}
}
but it fails, while debuggind the compiler doesn't get inside get{} and set{}.
After that, I created a UserControl item, but it must be deriverd from System.Web.UI.UserControl, not System.Web.UI.WebControls.TextBox to get it work (there's an exception which points to that)
So, how can I do that ?
First you have to register your control in your .aspx page like that:
<%# Register TagPrefix="customControls" Namespace="WebApplication.Custom.Controls" Assembly="WebApplication"%>
Then you can call it using the markup
<customControls:TrimmedTextBuox ID="txtTrim" runat="server"/>
Plus you don't have to create another "text" property in your custom TextBox. Instead, it can be done like that:
namespace WebApplication.Custom.Controls
{
public class TrimmedTextBuox : TextBox
{
public override string Text
{
get
{
return base.Text;
}
set
{
if (!String.IsNullOrEmpty(value))
base.Text = value.Trim();
}
}
}
}
This will trim recursively all text boxes before inserting.
public static void trimRecursive(Control root)
{
foreach (Control control in root.Controls)
{
if (control is TextBox)
{
var textbox = control as TextBox;
textbox.Text = textbox.Text.Trim();
}
else
{
trimRecursive(control);
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
trimRecursive(Page);
}
Simple Solution to your problem is to hide the Text property of your base class by using new keyword. sample code...
public class TrimmedTextBox : TextBox
{
public new string Text
{
get
{
var t = (string) GetValue(TextProperty);
return t != null ? t.Trim() : string.Empty;
}
}
}
For more info about how new keyword with property works refrer to this SO Question

Trim all user input strings in ASP.NET Web Forms

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();
}
}
}

Removing Controls from asp.net page at runtime

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? :)

C# ErrorProvider Want to know if any are Active

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 :-)

Make all Controls on a Form read-only at once

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);
}

Categories