diff --git a/Pilz.UI.Telerik/Controls/RadValidationProvider/IRadValidationRuleEx.cs b/Pilz.UI.Telerik/Controls/RadValidationProvider/IRadValidationRuleEx.cs
new file mode 100644
index 0000000..f3ce290
--- /dev/null
+++ b/Pilz.UI.Telerik/Controls/RadValidationProvider/IRadValidationRuleEx.cs
@@ -0,0 +1,65 @@
+//using System.Linq;
+using Telerik.WinControls;
+
+namespace Pilz.UI.Telerik.Controls.RadValidationProvider;
+
+
+///
+/// Represent a base for RadValidation
+///
+public interface IRadValidationRuleEx
+{
+
+ ///
+ /// Represents the List of Controls that belongs to this Rule.
+ ///
+ List Controls { get; set; }
+
+ ///
+ /// Gets or sets the ToolTip Text.
+ ///
+ string ToolTipText { get; set; }
+
+ ///
+ /// Gets or sets ToolTip Title.
+ ///
+ string ToolTipTitle { get; set; }
+ ///
+ /// Enable ot disable the ToolTip showing when validation fails.
+ ///
+ bool AutoToolTip { get; set; }
+
+ ///
+ /// Get the Rule Expression e.g. 'Value > 5'
+ ///
+ string Expression { get; }
+
+
+ ///
+ /// Sets or sets the PropertyName which will be evaluated. For example 'Value' Property.
+ ///
+ string PropertyName { get; set; }
+
+ ///
+ /// Add a RadControl descendant to the Rule's Controls collection.
+ ///
+ /// RadControl descendant instance
+ void AddControl(RadControl control);
+
+ ///
+ /// Remove a RadControl descendant from the Rule's Controls collection.
+ ///
+ /// RadControl descendant instance
+ void RemoveControl(RadControl control);
+ string ToString();
+
+ ///
+ /// The Value of the rule. This Value will be evaluated against the Property.
+ ///
+ object Value { get; }
+
+ ///
+ /// Turn On or Off the CaseSensitive evaluation.
+ ///
+ bool CaseSensitive { get; set; }
+}
diff --git a/Pilz.UI.Telerik/Controls/RadValidationProvider/RadCompositeValidationRuleEx.cs b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadCompositeValidationRuleEx.cs
new file mode 100644
index 0000000..ba89ef6
--- /dev/null
+++ b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadCompositeValidationRuleEx.cs
@@ -0,0 +1,179 @@
+using System.ComponentModel;
+using System.Drawing.Design;
+using Telerik.WinControls;
+
+//using System.Linq;
+using Telerik.WinControls.Data;
+
+namespace Pilz.UI.Telerik.Controls.RadValidationProvider;
+
+///
+/// RadCompositeValidationRule evaluates two or more RadValidationRules or RadValidationRuleWithTargetControl
+/// and combines their with AND or OR operator.
+///
+public class RadCompositeValidationRuleEx : CompositeFilterDescriptor, IRadValidationRuleEx
+{
+ private bool caseSensitive = false;
+ private string toolTipText = "";
+ private string toolTipTitle = "Validation Failed";
+ private bool autoToolTip = true;
+
+ ///
+ /// Gets or Sets a collection with RadControl descendants that belongs this Rule.
+ ///
+ [Editor(DesignerConsts.RadValidationRuleAssociatedControlsEditorString, typeof(UITypeEditor))]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public List Controls
+ {
+ get
+ {
+ List controls = [];
+ foreach (IRadValidationRuleEx validationRule in ValidationRules)
+ {
+ foreach (Control control in validationRule.Controls)
+ {
+ if (!controls.Contains(control))
+ {
+ controls.Add(control);
+ }
+ }
+ }
+
+ return controls;
+ }
+ set
+ {
+ foreach (RadControl control in value)
+ {
+ AddControl(control);
+ }
+ }
+ }
+
+ ///
+ /// Associates this rule and all controls in ValidationRules collection with the specified RadControl descendant.
+ ///
+ /// A RadControl descendant that represents the editor.
+
+ public virtual void AddControl(RadControl control)
+ {
+ if (control == null)
+ {
+ return;
+ }
+
+ foreach (IRadValidationRuleEx validationRule in ValidationRules)
+ {
+ if (!validationRule.Controls.Contains(control))
+ {
+ validationRule.AddControl(control);
+ }
+ }
+ }
+
+ ///
+ /// Removes the specified RadControl descendant from this rule and from all controls in ValidationRules collection .
+ ///
+ /// A RadControl descendant that represents the editor.
+ public void RemoveControl(RadControl control)
+ {
+ if (control == null)
+ {
+ return;
+ }
+
+ foreach (IRadValidationRuleEx validationRule in ValidationRules)
+ {
+ while (validationRule.Controls.Contains(control))
+ {
+ validationRule.RemoveControl(control);
+ }
+ }
+ }
+
+
+ ///
+ /// Inherit property. Not used in RadCompositeValidationRule.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ [Browsable(false)]
+ public override FilterDescriptorCollection FilterDescriptors { get { return base.FilterDescriptors; } }
+
+ ///
+ /// Gets or Sets the collection of ValidationRules that belongs to this RadValidationProvider.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
+ Editor(DesignerConsts.RadValidationProviderCompositeConditionsCollectionEditorString, typeof(UITypeEditor)),
+ Category(RadDesignCategory.DataCategory),
+ Description("Gets a collection representing the Rules in this CompositeValidationRule.")]
+ public FilterDescriptorCollection ValidationRules
+ {
+ get
+ {
+ return base.FilterDescriptors;
+ }
+ }
+
+ ///
+ /// Gets the Rule expression.
+ ///
+ /// The Rule expression.
+ public override string Expression { get { return base.Expression; } }
+
+ ///
+ /// Gets or Sets the ToolTip Text.
+ ///
+ [DefaultValue("")]
+ public string ToolTipText { get { return toolTipText; } set { toolTipText = value; } }
+
+ ///
+ /// Gets or Sets the ToolTip Title Text. This text will be shown as ToolTip Title text when rule validation fails.
+ ///
+ [DefaultValue("Validation Failed")]
+ public string ToolTipTitle { get { return toolTipTitle; } set { toolTipTitle = value; } }
+
+ ///
+ /// Inherited property. Not used in the Rule.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Browsable(false)]
+ public override bool IsFilterEditor
+ {
+ get { return base.IsFilterEditor; }
+ set { base.IsFilterEditor = value; }
+ }
+
+ ///
+ /// Enable or Disable the ToolTip when validation fails.
+ ///
+ [DefaultValue(true)]
+ public bool AutoToolTip
+ {
+ get { return autoToolTip; }
+ set { autoToolTip = value; }
+ }
+
+ ///
+ /// Inherited property. Not used in the Rule.
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public override object Value { get { return base.Value; } set { base.Value = value; } }
+
+ ///
+ /// Enable or Disable the case sensitive Rule's Like operator.
+ ///
+ [DefaultValue(false)]
+ public bool CaseSensitive
+ {
+ get
+ {
+ return caseSensitive;
+ }
+ set
+ {
+ caseSensitive = value;
+ }
+ }
+}
diff --git a/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationEventArgsEx.cs b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationEventArgsEx.cs
new file mode 100644
index 0000000..f3a0f28
--- /dev/null
+++ b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationEventArgsEx.cs
@@ -0,0 +1,109 @@
+using Telerik.WinControls;
+using Telerik.WinControls.UI;
+
+namespace Pilz.UI.Telerik.Controls.RadValidationProvider;
+
+public delegate void RadValidationEventHandlerEx(object sender, RadValidationEventArgsEx e);
+
+public class RadValidationEventArgsEx : EventArgs
+{
+ const string DefaultToolTipTitle = "Validation Failed";
+ private readonly Control control;
+ private readonly IRadValidationRuleEx validationRule;
+ private bool displayIconAndToolTip = true;
+ private string errorTitle = DefaultToolTipTitle;
+ private bool enableToolTipShadow = true;
+
+ ///
+ /// Gets or Sets the ValidationHelperElement descendant that contains the Error image. This element is set next to the validated control.
+ ///
+ public ValidationHelperElement ValidationHelperElement { get; set; }
+
+ ///
+ /// Gets or Sets the result from the rule evaluation.
+ ///
+ public bool IsValid { get; set; }
+
+ ///
+ /// Get the Control which is evaluated in the ValidationRule.
+ ///
+ public Control Control { get { return control; } }
+
+ ///
+ /// Error image that will displayed next to the Validated control.
+ ///
+ public Image ErrorImage { get; set; }
+ ///
+ /// Error SVG image that will displayed next to the Validated control.
+ ///
+ public RadSvgImage ErrorSvgImage { get; set; }
+ ///
+ /// Gets or sets the custom ToolTip for the Rule.
+ ///
+ public ToolTip ToolTip { get; set; }
+
+ ///
+ /// Sets the X position of the ToolTip.
+ ///
+ public int? ToolTipX { get; set; }
+
+ ///
+ /// Sets the Y position of the ToolTip.
+ ///
+ public int? ToolTipY { get; set; }
+
+ ///
+ /// Sets the ToolTip's duration in Milliseconds
+ ///
+ public int? ToolTipDuration { get; set; }
+
+ ///
+ /// The Rule that fires this event.
+ ///
+ public IRadValidationRuleEx ValidationRule { get { return validationRule; } }
+
+ ///
+ /// Sets or Gets the ToolTip's Text.
+ ///
+ public string ErrorText { get; set; }
+
+ ///
+ /// Sets or Gets the ToolTip's Title.
+ ///
+ public string ErrorTitle { get { return errorTitle; } set { errorTitle = value; } }
+
+ ///
+ /// Enable or Disable the Error border and error image.
+ ///
+ public bool DisplayIconAndToolTip { get { return displayIconAndToolTip; } set { displayIconAndToolTip = value; } }
+
+ ///
+ /// Enable or Disable the ToolTip shadow.
+ ///
+ public bool EnableToolTipShadow { get { return enableToolTipShadow; } set { enableToolTipShadow = value; } }
+
+ ///
+ /// Construct the RadValidationEventArgs object.
+ ///
+ /// The validated control. Must be RadEditorControl descendant
+ /// Erorr image
+ /// Error text
+ /// Error title
+ /// Validation rule
+ /// Validatation failed
+ public RadValidationEventArgsEx(
+ Control control,
+ Image errorImage,
+ string errorText,
+ string errorTitle,
+ IRadValidationRuleEx validationRule,
+ bool isFailed)
+ {
+ this.control = control;
+ ErrorImage = errorImage;
+ ErrorText = errorText;
+ this.validationRule = validationRule;
+ IsValid = !isFailed;
+ this.errorTitle = errorTitle;
+ }
+}
diff --git a/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationProviderEx.cs b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationProviderEx.cs
new file mode 100644
index 0000000..7aa08ba
--- /dev/null
+++ b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationProviderEx.cs
@@ -0,0 +1,706 @@
+using System.ComponentModel;
+using System.Drawing.Design;
+using System.Globalization;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Telerik.Data.Expressions;
+using Telerik.WinControls;
+using Telerik.WinControls.Data;
+using Telerik.WinControls.Design;
+using Telerik.WinControls.UI;
+
+namespace Pilz.UI.Telerik.Controls.RadValidationProvider;
+
+///
+/// Provides a validation management for RadControl descendant editors.
+///
+[ToolboxItem(true)]
+[TelerikToolboxCategory(ToolboxGroupStrings.EditorsGroup)]
+[Designer(DesignerConsts.RadValidationProviderDesignerString)]
+[ProvideProperty("ValidationRule", typeof(RadControl))]
+[ProvideProperty("IconAlignment", typeof(RadControl))]
+[ProvideProperty("IconPadding", typeof(RadControl))]
+public class RadValidationProviderEx : Component, ISupportInitialize, IExtenderProvider
+{
+ #region Externs
+
+ [DllImport("user32.dll", EntryPoint = "GetClassLong")]
+ private static extern int GetClassLong(nint hWnd, int nIndex);
+ [DllImport("user32.dll", EntryPoint = "SetClassLong")]
+ private static extern int SetClassLong(nint hWnd, int nIndex, int dwNewLong);
+ #endregion
+
+ #region Fields
+
+ private FilterDescriptorCollection validationRules = [];
+ private ValidationMode validationMode = ValidationMode.OnValidating;
+ private Dictionary errorIconAlignments = [];
+ private Dictionary errorIconPadding = [];
+ private Dictionary controlsToToolTips = [];
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Occurs before a RadControl is being validated.
+ ///
+ public event RadValidationEventHandlerEx ControlValidation;
+
+ ///
+ /// Occurs when the ValidionMode property changed.
+ ///
+ public event EventHandler ValidationModeChanged;
+
+ ///
+ /// Gets or Sets the ValidationMode.
+ ///
+ [DefaultValue(typeof(ValidationMode), "OnValidating")]
+ public ValidationMode ValidationMode
+ {
+ get { return validationMode; }
+ set
+ {
+ if (validationMode != value)
+ {
+ validationMode = value;
+ CallOnValidationChanged();
+ }
+ }
+ }
+
+ ///
+ /// Gets or Sets the collection of ValidationRules that belongs to this RadValidationProvider.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
+ Editor(DesignerConsts.RadValidationProviderItemCollectionDesignerString, typeof(UITypeEditor)),
+ Category(RadDesignCategory.DataCategory)]
+ [Description("Gets a collection representing the Conditions in this ValidationProvider.")]
+ public FilterDescriptorCollection ValidationRules
+ {
+ get
+ {
+ return validationRules;
+ }
+ set
+ {
+ validationRules = value;
+ }
+ }
+ #endregion
+
+ #region Cstor
+
+ ///
+ /// Initializes a new instance of the RadValidationProvider class.
+ ///
+ public RadValidationProviderEx()
+ {
+ validationRules.PropertyChanged += ValidationRules_PropertyChanged;
+ }
+
+ ///
+ /// Initializes a new instance of the RadValidationProvider class with the specified container control.
+ ///
+ /// An object that implements the interface, and owns the created object.
+ public RadValidationProviderEx(IContainer container) : this()
+ {
+ container.Add(this);
+ }
+
+ #endregion
+
+ #region Public methods
+
+ ///
+ /// Remove a specific RadControl from the validation rules.
+ ///
+ /// A RadControl descendant.
+ public void RemoveControlFromRules(RadControl editorControl)
+ {
+ foreach (IRadValidationRuleEx rule in ValidationRules)
+ {
+ RemoveValidationRule(editorControl, rule);
+ }
+ }
+
+ ///
+ /// Remove a validation rule associated with the specified RadControl descendant.
+ ///
+ /// A RadControl descendant.
+ /// Rule to remove.
+ public void RemoveValidationRule(RadControl editorControl, IRadValidationRuleEx ruleToRemove)
+ {
+ ruleToRemove.RemoveControl(editorControl);
+ }
+
+ public void BeginInit()
+ {
+ }
+
+ public void EndInit()
+ {
+ EnsureEventSubscribe();
+ }
+
+ ///
+ /// Validates all editors associated with the RadControl.
+ ///
+ /// true if all editors has been successfully validated; otherwise false.
+ public bool ValidateAll()
+ {
+ var valid = true;
+
+ AssociatedControls.ForEach(control =>
+ {
+ if (!Validate(control))
+ valid = false;
+ });
+
+ return valid;
+ }
+
+ ///
+ /// Validates the specified editor associated with the RadControl.
+ ///
+ /// A RadControl or descendant that represents the editor to be validated.
+ /// true if the editor has been successfully validated; otherwise false.
+ public bool Validate(RadControl control)
+ {
+ return ValidateCore(control, EventArgs.Empty);
+ }
+
+ ///
+ /// Sets the alignment of an error icon for the specified control.
+ ///
+ /// A target RadControl.
+ /// An value that specifies the alignment to be set for the RadControl.
+
+ public void SetIconAlignment(RadControl control, ErrorIconAlignment errorIconAlignment)
+ {
+ if (!errorIconAlignments.ContainsKey(control))
+ errorIconAlignments.Add(control, errorIconAlignment);
+ else
+ {
+ errorIconAlignments[control] = errorIconAlignment;
+ }
+ }
+
+ ///
+ /// Get the alignment of an error icon for the specified RadControl.
+ ///
+ /// A target control.
+ /// An value.
+ [DefaultValue(ErrorIconAlignment.MiddleRight)]
+ public ErrorIconAlignment GetIconAlignment(RadControl control)
+ {
+ if (errorIconAlignments.ContainsKey(control))
+ return errorIconAlignments[control];
+
+ return ErrorIconAlignment.MiddleRight;
+ }
+
+ /// Sets the amount of extra space to leave between the specified control and the error icon.
+ /// The to set the padding for.
+ /// The padding to add between the icon and the .
+ public void SetIconPadding(RadControl control, Padding errorIconPadding)
+ {
+ if (!this.errorIconPadding.ContainsKey(control))
+ this.errorIconPadding.Add(control, errorIconPadding);
+ else
+ {
+ this.errorIconPadding[control] = errorIconPadding;
+ }
+ }
+
+ /// Returns the amount of extra space to leave next to the error icon.
+ /// The padding to leave between the icon and the control.
+ /// The control to get the padding for.
+ [DefaultValue(typeof(Padding), "1, 1, 1, 1")]
+ public Padding GetIconPadding(RadControl control)
+ {
+ if (errorIconPadding.ContainsKey(control))
+ return errorIconPadding[control];
+
+ return new Padding(1);
+ }
+ protected virtual void OnControlValidation(RadValidationEventArgsEx e)
+ {
+ if (ControlValidation != null)
+ ControlValidation(this, e);
+ }
+
+ private void ValidationRules_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "Item[]")
+ EnsureEventSubscribe();
+ }
+
+ ///
+ /// Removes a rules associated with the control.
+ ///
+ /// A RadControl descendant
+ public void RemoveRules(RadControl control)
+ {
+ for (var i = validationRules.Count - 1; i >= 0; i--)
+ {
+ if (((IRadValidationRuleEx)validationRules[i]).Controls.Contains(control))
+ validationRules.RemoveAt(i);
+ }
+ }
+
+ ///
+ /// Gets the collection of the controls whose values are validated.
+ ///
+ [Browsable(false)]
+ public List AssociatedControls
+ {
+ get => [.. ValidationRules.OfType().SelectMany(n => n.Controls).OfType()];
+ }
+
+ ///
+ /// Clear the visual indication for the validation error.
+ ///
+ public virtual void ClearErrorStatus()
+ {
+ var controls = AssociatedControls;
+ for (var i = 0; i < controls.Count; i++)
+ {
+ ClearErrorStatus(controls[i]);
+ }
+ }
+
+ ///
+ /// Clear the visual indication for the validation error.
+ ///
+ /// A RadControl descendant
+ public virtual void ClearErrorStatus(RadControl associatedControl)
+ {
+ var children = associatedControl.RootElement.Children;
+ if (children.Count == 0)
+ return;
+
+ var editorControl = associatedControl;
+ var controlElement = TryFindControlElement(children);//first non ValidationIconElement is a Main Element for the Control.
+ if (controlElement == null)
+ return;
+
+ var border = ValidationHelperElement.GetBorder(children);
+
+ ValidationHelperElement.RestoreBorderColor(border);
+ associatedControl.ElementTree.ApplyThemeToElementTree();
+ for (var i = children.Count - 1; i >= 0; --i)
+ {
+ if (associatedControl.RootElement.Children[i] is ValidationHelperElement)
+ associatedControl.RootElement.Children.RemoveAt(i);
+ }
+
+ controlElement.PositionOffset = new SizeF();
+ controlElement.MaxSize = new Size();
+ associatedControl.RootElement.InvalidateMeasure(true);
+ associatedControl.RootElement.UpdateLayout();
+ }
+
+
+ #endregion
+
+ #region Private methods
+
+ private void AssociatedControl_TextChanged(object sender, EventArgs e)
+ {
+ if (ValidationMode == ValidationMode.OnTextChange)
+ ValidateCore(sender, e);
+ }
+
+ private void AssociatedControl_Validating(object sender, CancelEventArgs e)
+ {
+ if (ValidationMode == ValidationMode.OnValidating)
+ ValidateCore(sender, e);
+ }
+
+ internal static object GetSubPropertyValue(object control, string fieldName)
+ {
+ var names = fieldName.Split('.');
+ if (names.Length < 2)
+ return control.GetType().GetProperty(fieldName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(control, null);
+
+ var innerValue = control.GetType().GetProperty(names[0], BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(control, null);
+ var index = 1;
+ while (index < names.Length && innerValue != null)
+ {
+ innerValue = innerValue.GetType().GetProperty(names[index], BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(innerValue, null);
+ index++;
+ }
+
+ return innerValue;
+ }
+
+ private bool ValidateCore(object sender, EventArgs e)
+ {
+ var control = sender as Control;
+
+ foreach (IRadValidationRuleEx ruleToEvaluete in ValidationRules)
+ {
+ if (!ruleToEvaluete.Controls.Contains(control))
+ continue;
+
+ var context = new ExpressionContext();
+ context.Clear();
+ var value = GetSubPropertyValue(control, ruleToEvaluete.PropertyName);//control.GetType().GetProperty(ruleToEvaluete.PropertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(control, null);
+ if (value != null && ruleToEvaluete.Value != null)
+ {
+ try
+ {
+ // Fix for 441010
+ // Try to retrieve Culture from the control
+ var cultureInfo = RetrieveCulture(control);
+ value = Convert.ChangeType(value, ruleToEvaluete.Value.GetType(), cultureInfo);
+ }
+ catch
+ { }
+ }
+
+ context.Add(ruleToEvaluete.PropertyName, value);
+
+ if (string.IsNullOrEmpty(ruleToEvaluete.Expression))
+ continue;
+
+ var node = DataUtils.Parse(ruleToEvaluete.Expression, ruleToEvaluete.CaseSensitive);
+ var result = node.Eval(null, context);
+
+ if (result is bool)
+ {
+ var boolResult = (bool)result;
+ var validationEventArgs = FireValidationEvent(!boolResult, (RadControl)sender, ruleToEvaluete);
+ if (validationEventArgs.DisplayIconAndToolTip)
+ boolResult = AddOrRemoveImage(validationEventArgs, (RadControl)sender, ruleToEvaluete);
+ else
+ {
+ boolResult = validationEventArgs.IsValid;
+ }
+
+ var cancelEventArgs = e as CancelEventArgs;
+ if (cancelEventArgs != null)
+ cancelEventArgs.Cancel = !boolResult;
+
+ if (!boolResult)
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private bool AddOrRemoveImage(RadValidationEventArgsEx validationEventArgs, RadControl associatedControl, IRadValidationRuleEx rule)
+ {
+ var children = associatedControl.RootElement.Children;
+ if (children.Count == 0)
+ return true;
+
+ var editorControl = associatedControl;
+ var controlElement = TryFindControlElement(children);//first non ValidationIconElement is a Main Element for the Control.
+ if (controlElement == null)
+ return true;
+
+ var element = validationEventArgs.ValidationHelperElement;
+ var border = ValidationHelperElement.GetBorder(children);
+
+ var truncateSize = false;
+ if (!validationEventArgs.IsValid)
+ {
+ element ??= new ValidationHelperElement();
+
+ if (!children.Contains(element))
+ truncateSize = true;//size of the control should be truncate
+
+ element.ToolTipText = validationEventArgs.ErrorText;
+ element.AutoToolTip = true;
+ element.StretchHorizontally =
+ element.StretchVertically = false;
+ element.Image = validationEventArgs.ErrorImage;
+ element.SvgImage = validationEventArgs.ErrorSvgImage;
+ element.Alignment = ToContentAligment(GetIconAlignment(editorControl));
+ element.Padding = GetIconPadding(editorControl);
+ if (truncateSize)
+ {
+ var elementSize = TelerikDpiHelper.ScaleSizeF(MeasurementControl.ThreadInstance.GetDesiredSize(element, new SizeF(float.MaxValue, float.MaxValue)), controlElement.DpiScaleFactor);
+ var width = (controlElement.Size.Width - elementSize.Width) / controlElement.DpiScaleFactor.Width;
+ controlElement.MaxSize = new Size((int)width, controlElement.MaxSize.Height);
+
+ associatedControl.RootElement.ResetValue(VisualElement.BackColorProperty, ValueResetFlags.Style);
+ if (element.IsRightLocated)
+ children.Add(element);
+ else
+ {
+ controlElement.PositionOffset = new SizeF(elementSize.Width, 0);
+ children.Insert(0, element);
+ }
+ }
+
+ ValidationHelperElement.SetBorderColor(border);
+ if (rule.AutoToolTip)
+ {
+ var toolTipX = validationEventArgs.ToolTipX.HasValue ? validationEventArgs.ToolTipX.Value : 0;
+ var toolTipY = validationEventArgs.ToolTipY.HasValue ? validationEventArgs.ToolTipY.Value : associatedControl.Height + 1;
+ var toolTipDuration = validationEventArgs.ToolTipDuration.HasValue ? validationEventArgs.ToolTipDuration.Value : 2000;
+
+ if (validationEventArgs.ToolTip != null)
+ {
+ associatedControl.Disposed -= AssociatedControl_Disposed;
+ associatedControl.Disposed += AssociatedControl_Disposed;
+ if (controlsToToolTips.ContainsKey(associatedControl))
+ controlsToToolTips[associatedControl] = validationEventArgs.ToolTip;
+ else
+ {
+ controlsToToolTips.Add(associatedControl, validationEventArgs.ToolTip);
+ }
+
+ if (!validationEventArgs.EnableToolTipShadow)
+ ToolTipRemoveShadow(validationEventArgs.ToolTip);
+ validationEventArgs.ToolTip.Show(element.ToolTipText, associatedControl, toolTipX, toolTipY, toolTipDuration);
+ }
+ else
+ {
+ ShowToolTip(associatedControl, element.ToolTipText, validationEventArgs.ErrorTitle, toolTipX, toolTipY, toolTipDuration, validationEventArgs.EnableToolTipShadow);
+ }
+ }
+ }
+ else
+ {
+ ValidationHelperElement.RestoreBorderColor(border);
+ associatedControl.ElementTree.ApplyThemeToElementTree();
+ for (var i = children.Count - 1; i >= 0; --i)
+ {
+ if (associatedControl.RootElement.Children[i] is ValidationHelperElement)
+ associatedControl.RootElement.Children.RemoveAt(i);
+ }
+
+ controlElement.PositionOffset = new SizeF();
+ controlElement.MaxSize = new Size();
+ }
+
+ associatedControl.RootElement.InvalidateMeasure(true);
+ associatedControl.RootElement.UpdateLayout();
+ associatedControl.Refresh();
+ return validationEventArgs.IsValid;
+ }
+
+ private void ToolTipRemoveShadow(ToolTip toolTip)
+ {
+ var hwnd = (nint)typeof(ToolTip).GetProperty("Handle",
+ BindingFlags.NonPublic |
+ BindingFlags.Instance).GetValue(toolTip, null);
+ var cs = GetClassLong(hwnd, NativeMethods.GCL_STYLE);
+ if ((cs & NativeMethods.CS_DROPSHADOW) == NativeMethods.CS_DROPSHADOW)
+ {
+ cs &= ~NativeMethods.CS_DROPSHADOW;
+ SetClassLong(hwnd, NativeMethods.GCL_STYLE, cs);
+ }
+ }
+
+ private void AssociatedControl_Disposed(object sender, EventArgs e)
+ {
+ var radControl = sender as RadControl;
+ radControl.Disposed -= AssociatedControl_Disposed;
+ if (radControl != null && controlsToToolTips.ContainsKey(radControl))
+ {
+ var toolTip = controlsToToolTips[radControl];
+ if (toolTip != null)
+ {
+ controlsToToolTips.Remove(radControl);
+ toolTip.Dispose();
+ toolTip = null;
+ }
+ }
+ }
+
+ protected virtual void ShowToolTip(RadControl associatedControl, string toolTipText, string toolTipTitle, int toolTipX, int toolTipY, int toolTipDuration, bool enableToolTipShadow)
+ {
+ var toolTip = new ToolTip
+ {
+ ToolTipTitle = toolTipTitle,
+
+ BackColor = Color.Red,
+ ForeColor = Color.White,
+ OwnerDraw = true,
+ InitialDelay = 0,
+ AutoPopDelay = toolTipDuration
+ };
+ if (!enableToolTipShadow)
+ ToolTipRemoveShadow(toolTip);
+
+ toolTip.Draw += delegate (object sender, DrawToolTipEventArgs e)
+ {
+ e.DrawBackground();
+ e.DrawBorder();
+ using (var brush = new SolidBrush(Color.White))
+ using (var titleFont = new Font("Segoe UI", 8f, FontStyle.Bold))
+ using (var textFont = new Font("Segoe UI", 8f, FontStyle.Regular))
+ {
+ e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
+ if (!string.IsNullOrEmpty(toolTipTitle))
+ {
+ e.Graphics.DrawString(toolTipTitle, titleFont, brush, new Point(2, 2));
+ e.Graphics.DrawString(toolTipText, textFont, brush, new Point(2, 19));
+ }
+ else
+ {
+ e.Graphics.DrawString(toolTipText, textFont, brush, new Point(2, 2));
+ }
+ }
+ };
+
+ if (controlsToToolTips.ContainsKey(associatedControl))
+ controlsToToolTips[associatedControl] = toolTip;
+ else
+ {
+ controlsToToolTips.Add(associatedControl, toolTip);
+ }
+
+ associatedControl.Disposed -= AssociatedControl_Disposed;
+ associatedControl.Disposed += AssociatedControl_Disposed;
+
+ toolTip.Show(toolTipText, associatedControl, toolTipX, toolTipY, toolTipDuration);
+ }
+
+ private ContentAlignment ToContentAligment(ErrorIconAlignment errorIconAlignment)
+ {
+ switch (errorIconAlignment)
+ {
+ case ErrorIconAlignment.TopLeft:
+ return ContentAlignment.TopLeft;
+ case ErrorIconAlignment.TopRight:
+ return ContentAlignment.TopRight;
+ case ErrorIconAlignment.MiddleLeft:
+ return ContentAlignment.MiddleLeft;
+ case ErrorIconAlignment.MiddleRight:
+ return ContentAlignment.MiddleRight;
+ case ErrorIconAlignment.BottomLeft:
+ return ContentAlignment.BottomLeft;
+ case ErrorIconAlignment.BottomRight:
+ return ContentAlignment.BottomRight;
+ default:
+ return ContentAlignment.MiddleRight;
+ }
+ }
+
+ private static RadElement TryFindControlElement(RadElementCollection children)
+ {
+ RadElement controlElement = null;
+ foreach (var child in children)
+ {
+ if (child is not ValidationHelperElement)
+ {
+ controlElement = child;
+ break;
+ }
+ }
+
+ return controlElement;
+ }
+
+ protected virtual RadValidationEventArgsEx FireValidationEvent(bool isNotValid, RadControl associatedControl, IRadValidationRuleEx rule)
+ {
+ Image image = ResourceHelper.ImageFromResource(typeof(global::Telerik.WinControls.UI.RadValidationProvider), "Telerik.WinControls.UI.Resources.error-icon.png");
+ var errorText = string.IsNullOrEmpty(rule.ToolTipText) ? rule.Expression : rule.ToolTipText;
+ var validationEventArgs = new RadValidationEventArgsEx(associatedControl, image, errorText, rule.ToolTipTitle, rule, isNotValid)
+ {
+ ValidationHelperElement = ValidationHelperElement.GetValidationElement(associatedControl.RootElement.Children)
+ };
+ validationEventArgs.ValidationHelperElement ??= new ValidationHelperElement();
+
+ OnControlValidation(validationEventArgs);
+ return validationEventArgs;
+ }
+
+ private void EnsureEventSubscribe()
+ {
+ foreach (IRadValidationRuleEx rule in ValidationRules)
+ {
+ var controls = rule.Controls;
+ foreach (var control in controls)
+ {
+ if (control != null)
+ {
+ control.Validating -= AssociatedControl_Validating;
+ control.Validating += AssociatedControl_Validating;
+ control.TextChanged -= AssociatedControl_TextChanged;
+ control.TextChanged += AssociatedControl_TextChanged;
+ }
+ }
+ }
+ }
+
+ private void CallOnValidationChanged()
+ {
+ if (ValidationModeChanged != null)
+ ValidationModeChanged(this, EventArgs.Empty);
+ }
+
+ private CultureInfo RetrieveCulture(Control control)
+ {
+ var cultureProperty = control.GetType().GetProperty("Culture", BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+ if (cultureProperty != null)
+ {
+ var cultureObject = cultureProperty.GetValue(control, null);
+ if (cultureObject != null && cultureObject is CultureInfo)
+ return cultureObject as CultureInfo;
+ }
+
+ return CultureInfo.CurrentCulture;
+ }
+
+ #endregion
+
+ #region ExtenderProvider Interface implementation
+
+ ///
+ /// Indicates whether a control can be extended.
+ ///
+ /// The control to be extended.
+ /// true if the control can be extended otherwise false.
+ public bool CanExtend(object extendee)
+ {
+ return extendee is RadControl;
+ }
+
+ ///
+ /// Associates a validation rule with the specified RadControl descendant.
+ ///
+ /// A RadControl descendant that represents the editor.
+ /// A RadValidationRule descendant that represents the validation rule.
+ [Editor(DesignerConsts.RadValidationRuleEditorString, typeof(UITypeEditor))]
+ public void SetValidationRule(RadControl control, FilterDescriptor rule)
+ {
+ var radValidationRule = rule as IRadValidationRuleEx;
+ if (radValidationRule == null)
+ RemoveControlFromRules(control);
+ else
+ {
+ radValidationRule.AddControl(control);
+ }
+ }
+
+ ///
+ /// Returns a validation rule associated with the specified RadControl descendant.
+ ///
+ /// A RadControl descendant.
+ /// A RadValidationRule descendant that represents the validation rule associated with the editor. Null if no validation rule is associated with the specified control.
+
+ [Editor(DesignerConsts.RadValidationRuleEditorString, typeof(UITypeEditor))]
+ public FilterDescriptor GetValidationRule(RadControl control)
+ {
+ IRadValidationRuleEx ruleToEvaluete = null;
+ foreach (IRadValidationRuleEx rule in ValidationRules)
+ {
+ if (rule.Controls.Contains(control))
+ {
+ ruleToEvaluete = rule;
+ break;
+ }
+ }
+
+ return ruleToEvaluete as FilterDescriptor;
+ }
+ #endregion
+}
diff --git a/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationRuleEx.cs b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationRuleEx.cs
new file mode 100644
index 0000000..2b253ca
--- /dev/null
+++ b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationRuleEx.cs
@@ -0,0 +1,154 @@
+using System.ComponentModel;
+using System.Drawing.Design;
+using Telerik.WinControls;
+using Telerik.WinControls.Data;
+
+namespace Pilz.UI.Telerik.Controls.RadValidationProvider;
+
+
+///
+/// RadValidationRule provides a validation logic which compares RadControl's Property with Rule's Value.
+///
+public class RadValidationRuleEx : FilterDescriptor, IRadValidationRuleEx
+{
+ #region Fields
+ private List controls = [];
+ private string toolTipText = string.Empty;
+ private bool caseSensitive = false;
+ private bool autoToolTip = true;
+ private string toolTipTitle = "Validation Failed";
+ #endregion
+
+ #region Cstor
+ public RadValidationRuleEx() : base()
+ {
+ this.PropertyName = "Text";
+ }
+
+ public RadValidationRuleEx(string propertyName, FilterOperator filterOperator, object value) : base(propertyName, filterOperator, value)
+ {
+ }
+
+ #endregion
+
+ ///
+ /// Associates this rule with the specified RadControl descendant.
+ ///
+ /// A RadControl descendant that represents the editor.
+
+ public virtual void AddControl(RadControl control)
+ {
+ if (control != null && !controls.Contains(control))
+ {
+ controls.Add(control);
+ }
+ }
+
+ ///
+ /// Removes the specified RadControl descendant from this rule.
+ ///
+ /// A RadControl descendant that represents the editor.
+ public virtual void RemoveControl(RadControl control)
+ {
+ if (control == null)
+ {
+ return;
+ }
+
+ while (controls.Contains(control))
+ {
+ controls.Remove(control);
+ }
+ }
+
+
+ ///
+ /// Inherit property. Not used in RadValidation Rule
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Browsable(false)]
+ public override bool IsFilterEditor
+ {
+ get { return base.IsFilterEditor; }
+ set { base.IsFilterEditor = value; }
+ }
+
+ ///
+ /// Associated RadControl descendants to this Rule
+ ///
+ [Editor(DesignerConsts.RadValidationRuleAssociatedControlsEditorString, typeof(UITypeEditor))]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
+ public List Controls
+ {
+ get
+ {
+ return controls;
+ }
+ set
+ {
+ controls = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the Value of this rule. Controls in the rule will be evaluated against this value.
+ ///
+ [Editor(DesignerConsts.RadValidationRuleValueEditorString, typeof(UITypeEditor))]
+ public override object Value
+ {
+ get { return base.Value; }
+ set { base.Value = value; }
+ }
+
+
+ ///
+ /// Gets or Sets the ToolTip Text. This text will be shown as ToolTip text when rule validation fails.
+ ///
+ [DefaultValue("")]
+ public string ToolTipText
+ {
+ get { return toolTipText; }
+ set { toolTipText = value; }
+ }
+
+ ///
+ /// Enable or Disable the ToolTip when validation fails.
+ ///
+ [DefaultValue(true)]
+ public bool AutoToolTip
+ {
+ get { return this.autoToolTip; }
+ set { this.autoToolTip = value; }
+ }
+
+ ///
+ /// Gets or Sets the ToolTip Title Text. This text will be shown as ToolTip Title text when rule validation fails.
+ ///
+ [DefaultValue("Validation Failed")]
+ public string ToolTipTitle
+ {
+ get { return this.toolTipTitle; }
+ set { this.toolTipTitle = value; }
+ }
+
+
+ ///
+ /// Enable or Disable the case sensitive Rule's Like operator.
+ ///
+ [DefaultValue(false)]
+ public bool CaseSensitive
+ {
+ get { return this.caseSensitive; }
+ set { this.caseSensitive = value; }
+ }
+
+ ///
+ /// The Name of the Property from Control. This Property will be evaluated against the Rule's Value property.
+ ///
+ [DefaultValue("Text")]
+ public override string PropertyName
+ {
+ get { return base.PropertyName; }
+ set { base.PropertyName = value; }
+ }
+}
diff --git a/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationRuleWithTargetControlEx.cs b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationRuleWithTargetControlEx.cs
new file mode 100644
index 0000000..fa5ee79
--- /dev/null
+++ b/Pilz.UI.Telerik/Controls/RadValidationProvider/RadValidationRuleWithTargetControlEx.cs
@@ -0,0 +1,99 @@
+using System.ComponentModel;
+//using System.Linq;
+
+namespace Pilz.UI.Telerik.Controls.RadValidationProvider;
+
+
+///
+/// RadValidationRuleWithTargetControl provides a validation logic which compares RadControl's Property with TargetControl's property.
+///
+public class RadValidationRuleWithTargetControlEx : RadValidationRuleEx
+{
+ private string sourceControlPropertyName = "Text";
+ public RadValidationRuleWithTargetControlEx()
+ {
+
+ }
+
+ ///
+ /// Gets or sets the Target Control. This control's property value will be used in the Rule evaluation.
+ ///
+ [DefaultValue(null)]
+ public Control TargetControl { get; set; }
+
+
+ ///
+ /// The name of the property that will be used in the Rule evaluation.
+ ///
+ [DefaultValue("Text")]
+ public string TargetControlPropertyName
+ {
+ get { return this.sourceControlPropertyName; }
+ set { this.sourceControlPropertyName = value; }
+ }
+
+ ///
+ /// Gets the Rule expression.
+ ///
+ /// The Rule expression.
+ public override string Expression
+ {
+ get
+ {
+ if (this.TargetControl != null)
+ {
+ this.Value = this.CalculateValue();
+ }
+
+ return base.Expression;
+ }
+ }
+
+ ///
+ /// Gets or sets the Value of this rule. Controls in the rule will be evaluated against this value.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public override object Value
+ {
+ get
+ {
+ if (this.TargetControl != null && !string.IsNullOrEmpty(this.TargetControlPropertyName))
+ {
+ try
+ {
+ base.Value = RadValidationProviderEx.GetSubPropertyValue(TargetControl, TargetControlPropertyName);//TargetControl.GetType().GetProperty(TargetControlPropertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(TargetControl, null);
+ }
+ catch { }
+
+ }
+ return base.Value;
+ }
+ set
+ {
+
+ base.Value = value;
+ }
+ }
+
+ protected virtual object CalculateValue()
+ {
+ if (this.TargetControl.Site != null)
+ {
+ return string.Format("{0}.{1}", this.TargetControl.Name, this.TargetControlPropertyName);
+ }
+
+ if (!string.IsNullOrEmpty(this.TargetControlPropertyName))
+ {
+ try
+ {
+ return RadValidationProviderEx.GetSubPropertyValue(TargetControl, TargetControlPropertyName);//this.TargetControl.GetType().GetProperty(this.TargetControlPropertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(this.TargetControl, null);
+ }
+ catch
+ { }
+ }
+
+ return null;
+ }
+}
diff --git a/Pilz.UI.Telerik/Dialogs/RadFlyoutBase.Statics.cs b/Pilz.UI.Telerik/Dialogs/RadFlyoutBase.Statics.cs
index 4315750..bd1ea71 100644
--- a/Pilz.UI.Telerik/Dialogs/RadFlyoutBase.Statics.cs
+++ b/Pilz.UI.Telerik/Dialogs/RadFlyoutBase.Statics.cs
@@ -1,6 +1,4 @@
-using System.Runtime.CompilerServices;
-using Telerik.WinControls;
-using Telerik.WinControls.Svg;
+using Telerik.WinControls;
using Telerik.WinControls.UI;
using Telerik.WinControls.UI.SplashScreen;
diff --git a/Pilz.UI.Telerik/Dialogs/RadFlyoutBase.cs b/Pilz.UI.Telerik/Dialogs/RadFlyoutBase.cs
index 0cfe454..a167f65 100644
--- a/Pilz.UI.Telerik/Dialogs/RadFlyoutBase.cs
+++ b/Pilz.UI.Telerik/Dialogs/RadFlyoutBase.cs
@@ -1,5 +1,7 @@
-using System.ComponentModel;
+using Pilz.UI.Telerik.Controls.RadValidationProvider;
+using System.ComponentModel;
using Telerik.WinControls;
+using Telerik.WinControls.Data;
using Telerik.WinControls.UI;
namespace Pilz.UI.Telerik.Dialogs;
@@ -13,6 +15,7 @@ public partial class RadFlyoutBase : UserControl
protected TableLayoutPanel tableLayoutPanel_ActionPanel;
protected TableLayoutPanel tableLayoutPanel_TitlePanel;
protected RadLabel radLabel_Title;
+ protected RadValidationProviderEx validationProvider = new();
public static RadSvgImage? CancelSvg { get; set; } = null;
public static RadSvgImage? ConfirmSvg { get; set; } = null;
@@ -95,6 +98,16 @@ public partial class RadFlyoutBase : UserControl
}
}
+ [Browsable(false)]
+ public FilterDescriptorCollection ValidationRules => validationProvider.ValidationRules;
+
+ [DefaultValue(typeof(ValidationMode), "OnValidating")]
+ public ValidationMode ValidationMode
+ {
+ get => validationProvider.ValidationMode;
+ set => validationProvider.ValidationMode = value;
+ }
+
protected RadFlyoutBase()
{
InitializeComponent();
@@ -251,7 +264,7 @@ public partial class RadFlyoutBase : UserControl
protected virtual bool ValidateOK()
{
- return true;
+ return validationProvider.ValidateAll();
}
protected virtual void SetShowTitlePanel()
diff --git a/Pilz.UI.Telerik/Extensions/RadFlyoutBaseExtensions.cs b/Pilz.UI.Telerik/Extensions/RadFlyoutBaseExtensions.cs
index 5342d88..7d120a5 100644
--- a/Pilz.UI.Telerik/Extensions/RadFlyoutBaseExtensions.cs
+++ b/Pilz.UI.Telerik/Extensions/RadFlyoutBaseExtensions.cs
@@ -1,5 +1,4 @@
using Pilz.UI.Telerik.Dialogs;
-using System.Diagnostics.CodeAnalysis;
namespace Pilz.UI.Telerik.Extensions.Extensions;
diff --git a/Pilz.UI.Telerik/Extensions/RadListDataItemCollectionExtensions.cs b/Pilz.UI.Telerik/Extensions/RadListDataItemCollectionExtensions.cs
index fc17e6f..ff56e54 100644
--- a/Pilz.UI.Telerik/Extensions/RadListDataItemCollectionExtensions.cs
+++ b/Pilz.UI.Telerik/Extensions/RadListDataItemCollectionExtensions.cs
@@ -19,8 +19,7 @@ public static class RadListDataItemCollectionExtensions
var values = Enum.GetValues(typeof(T));
var items = new List();
- if (format == null)
- format = v => Enum.GetName(typeof(T), v);
+ format ??= v => Enum.GetName(typeof(T), v);
if (clearCollection)
@this.Clear();
diff --git a/Pilz.UI.Telerik/Theming/ThemeHelper.cs b/Pilz.UI.Telerik/Theming/ThemeHelper.cs
index cfc424a..9ef8def 100644
--- a/Pilz.UI.Telerik/Theming/ThemeHelper.cs
+++ b/Pilz.UI.Telerik/Theming/ThemeHelper.cs
@@ -9,7 +9,7 @@ public static class ThemeHelper
{
ApplyApplicationTheme(theme, getLightTheme, getDarkTheme, getDarkTheme);
}
-
+
[Obsolete()]
public static void ApplyApplicationTheme(ApplicationTheme theme, Func getLightTheme, Func getDarkTheme, Func getGrayTheme)
{
@@ -43,7 +43,7 @@ public static class ThemeHelper
themeToUse = ApplicationTheme.Dark;
else
themeToUse = ApplicationTheme.Gray;
-
+
return getTheme(new(themeToUse, highContrast));
}