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