using System; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Windows.Forms; using Vanara.Extensions; using Vanara.PInvoke; using static Vanara.PInvoke.ComCtl32; using static Vanara.PInvoke.User32; namespace Vanara.Windows.Forms { /// An Internet Protocol (IP) address control allows the user to enter an IP address in an easily understood format. /// [DefaultEvent(nameof(FieldChanged)), DefaultProperty(nameof(Text))] //[Designer(typeof(IPAddressBoxDesigner))] public partial class IPAddressBox : Control { internal const string defaultText = "0.0.0.0"; private BorderStyle borderStyle = BorderStyle.Fixed3D; /// Initializes a new instance of the class. public IPAddressBox() { SetStyle(ControlStyles.FixedHeight, true); SetStyle(ControlStyles.StandardClick | ControlStyles.StandardDoubleClick | ControlStyles.UseTextForAccessibility | ControlStyles.UserPaint, false); } /// /// Occurs when one of the fields change. To change the value set in the control, set the /// property. /// public event EventHandler FieldChanged; /// Gets or sets the border style. /// The border style. [Category("Appearance"), DefaultValue(BorderStyle.Fixed3D), Description("")] public BorderStyle BorderStyle { get => borderStyle; set { if (borderStyle != value) { borderStyle = value; UpdateStyles(); RecreateHandle(); } } } /// Gets or sets the IP address. /// The IP address. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IPAddress IPAddress { get { uint ip = 0; SendMessage(IPAddressMessage.IPM_GETADDRESS, IntPtr.Zero, ref ip); return new IPAddress(GET_IPADDRESS(ip)); } set { if (value is null) { Clear(); return; } if (value.AddressFamily != AddressFamily.InterNetwork) throw new ArgumentException("Only IP v4 addresses are permissible."); try { var ip = MAKEIPADDRESS(value.GetAddressBytes()); SendMessage(IPAddressMessage.IPM_SETADDRESS, IntPtr.Zero, (IntPtr)unchecked((int)ip)); } catch (Exception e) { Debug.WriteLine($"set_IPAddress:{e}"); } } } /// Gets a value indicating whether the value is blank (0.0.0.0). /// true if the value is blank; otherwise, false. [DefaultValue(true), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsBlank => SendMessage(IPAddressMessage.IPM_ISBLANK).ToInt32() > 0; /// Gets the preferred height of the control. /// The preferred height of the control. [Category("Layout"), Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Description("")] public int PreferredHeight { get { var height = FontHeight; if (borderStyle != BorderStyle.None) height += SystemInformation.BorderSize.Height * 4 + 3; return height; } } /// [DefaultValue(defaultText)] public override string Text { get => base.Text; set { if (value == Name) return; if (!string.IsNullOrEmpty(value) && !System.Text.RegularExpressions.Regex.Match(value, @"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$").Success) throw new ArgumentException($"Invalid format. Text cannot be assigned a value of '{value}'.", nameof(Text)); if (value != base.Text) IPAddress = string.IsNullOrEmpty(value) ? null : IPAddress.Parse(value); } } /// protected override CreateParams CreateParams { get { var cp = base.CreateParams; cp.ClassName = WC_IPADDRESS; cp.ExStyle &= (~(int)WindowStylesEx.WS_EX_CLIENTEDGE); cp.Style &= (~(int)WindowStyles.WS_BORDER); switch (borderStyle) { case BorderStyle.Fixed3D: cp.ExStyle |= (int)WindowStylesEx.WS_EX_CLIENTEDGE; break; case BorderStyle.FixedSingle: cp.Style |= (int)WindowStyles.WS_BORDER; break; } cp.Height = 23; return cp; } } /// protected override Size DefaultSize => new Size(100, PreferredHeight); /// Clears the value and resets it to 0.0.0.0. public void Clear() => SendMessage(IPAddressMessage.IPM_CLEARADDRESS); /// public override Size GetPreferredSize(Size proposedConstraints) { const string measureString = " 255 . 255 . 255 . 255 "; // 3px vertical space is required between the text and the border to keep the last line from being clipped. This 3 pixel size // was added in everett and we do this to maintain compat. old everett behavior was FontHeight + [SystemInformation.BorderSize.Height // * 4 + 3] however the [ ] was only added if borderstyle was not none. var bordersAndPadding = SizeFromClientSize(Size.Empty) + Padding.Size; if (BorderStyle != BorderStyle.None) bordersAndPadding += new Size(0, 3); if (BorderStyle == BorderStyle.FixedSingle) { // VSWhidbey 321520: bump these by 2px to match BorderStyle.Fixed3D - they'll be omitted from the SizeFromClientSize call. bordersAndPadding.Width += 2; bordersAndPadding.Height += 2; } // Reduce constraints by border/padding size proposedConstraints -= bordersAndPadding; // Fit the text to the remaining space Fix for Dev10 var format = TextFormatFlags.NoPrefix; format |= TextFormatFlags.SingleLine; var textSize = TextRenderer.MeasureText(measureString, Font, proposedConstraints, format); // We use this old computation as a lower bound to ensure backwards compatibility. textSize.Height = Math.Max(textSize.Height, FontHeight); var preferredSize = textSize + bordersAndPadding; return preferredSize; } /// Sets the valid range for the specified field in the IP address control. /// A zero-based field index to which the range will be applied. /// The lower limit of the range (inclusive). /// The upper limit of the range (inclusive). /// field - Field must be a value from 0 to 3. public bool SetFieldRange(int field, byte minValue, byte maxValue) { if (field < 0 || field > 3) throw new ArgumentOutOfRangeException(nameof(field), @"Field must be a value from 0 to 3."); var ipr = MAKEIPRANGE(minValue, maxValue); return SendMessage(IPAddressMessage.IPM_SETRANGE, (IntPtr)field, ref ipr).ToInt32() > 0; } /// Raises the event. /// The instance containing the event data. protected void OnFieldChanged(IPAddressFieldChangedEventArgs e) => FieldChanged?.Invoke(this, e); /// Processes reflected notification messages. /// The Windows to process. /// true if message handled; otherwise false. protected virtual bool WmReflectNotify(ref Message m) { var hdr = m.LParam.ToStructure(); if (hdr.code == (int)IPAddressNotification.IPN_FIELDCHANGED) { var ipAddr = m.LParam.ToStructure(); var e = new IPAddressFieldChangedEventArgs(ipAddr.iField, ipAddr.iValue); OnFieldChanged(e); if (e.Value != ipAddr.iValue) Marshal.WriteInt32(m.LParam, Marshal.OffsetOf(typeof(NMIPADDRESS), "iValue").ToInt32(), e.Value); return true; } return false; } /// Processes Windows messages. /// The Windows to process. protected override void WndProc(ref Message m) { if (m.Msg == (int)WindowMessage.WM_REFLECT + (int)WindowMessage.WM_NOTIFY) if (WmReflectNotify(ref m)) return; base.WndProc(ref m); } private IntPtr SendMessage(IPAddressMessage msg, IntPtr wParam = default, IntPtr lParam = default) => this.SendMessage((uint)msg, wParam, lParam); private IntPtr SendMessage(IPAddressMessage msg, IntPtr wParam, ref uint lParam) => User32.SendMessage(Handle, msg, wParam, ref lParam); } /// Contains the arguments needed to handle the event. /// public class IPAddressFieldChangedEventArgs : EventArgs { /// Initializes a new instance of the class. /// The IP address field (0-3). /// The value for the indicated field. internal IPAddressFieldChangedEventArgs(int field, int value) { Field = field; Value = (byte)value; } /// The zero-based number of the field that was changed. /// The field number. public int Field { get; } /// /// The new value of the field specified in the property. This property can be set to any value that is within /// the range of the field and the control will place this new value in the field. /// /// The value set by the user on input and the value to place in the control on output. public byte Value { get; set; } } /*internal class IPAddressBoxDesigner : Design.RichControlDesigner { public override void InitializeNewComponent(IDictionary defaultValues) { base.InitializeNewComponent(defaultValues); var descriptor = TypeDescriptor.GetProperties(Control)["Text"]; if ((descriptor != null) && (descriptor.PropertyType == typeof(string)) && !descriptor.IsReadOnly && descriptor.IsBrowsable) descriptor.SetValue(Control, IPAddressBox.defaultText); } }*/ }