2023-09-24 17:26:46 -04:00
using System.Collections.Generic ;
2017-11-27 13:11:20 -05:00
using System.ComponentModel ;
2023-09-29 13:58:35 -04:00
using System.Diagnostics.CodeAnalysis ;
2017-11-27 13:11:20 -05:00
using System.Drawing ;
2019-11-22 20:50:54 -05:00
using System.Linq ;
2017-11-27 13:11:20 -05:00
using System.Reflection ;
using System.Windows.Forms ;
2023-03-31 11:47:53 -04:00
namespace Vanara.Windows.Forms ;
/// <summary>
/// An input dialog that automatically creates controls to collect the values of the object supplied via the <see cref="Data"/> property.
/// </summary>
public class InputDialog : CommonDialog
2017-11-27 13:11:20 -05:00
{
2023-09-29 13:58:35 -04:00
private object? data ;
2023-03-31 11:47:53 -04:00
2019-11-22 20:50:54 -05:00
/// <summary>
2023-03-31 11:47:53 -04:00
/// Gets or sets the data for the input dialog box. The data type will determine the type of input mechanism displayed. For simple
/// types, a <see cref="TextBox"/> with validation, or a <see cref="CheckBox"/> or a <see cref="ComboBox"/> will be displayed. For
/// classes and structures, all of the public, top-level, fields and properties will have input mechanisms shown for each. See
/// Remarks for more detail.
2019-11-22 20:50:54 -05:00
/// </summary>
2023-03-31 11:47:53 -04:00
/// <value>The data for the input dialog box.</value>
/// <remarks>TBD</remarks>
[DefaultValue(null), Browsable(false), Category("Data"), Description("The data for the input dialog box.")]
2023-09-29 13:58:35 -04:00
public object? Data
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
get = > data ;
set = > data = value ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>
/// Gets or sets the image to display on the top left corner of the dialog. This value can be <c>null</c> to display no image.
/// </summary>
/// <value>The image to display on the top left corner of the dialog.</value>
[DefaultValue(null), Category("Appearance"), Description("The image to display on the top left corner of the dialog.")]
[Localizable(true)]
2023-09-29 13:58:35 -04:00
public Image ? Image { get ; set ; }
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets the text prompt to display above all input options. This value can be <c>null</c>.</summary>
/// <value>The text prompt to display above all input options.</value>
[DefaultValue(null), Category("Appearance"), Description("The text prompt to display above all input options.")]
[Localizable(true), Bindable(true), Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
2023-09-29 13:58:35 -04:00
public string? Prompt { get ; set ; }
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets the input dialog box title.</summary>
/// <value>The input dialog box title. The default value is an empty string ("").</value>
[DefaultValue(""), Category("Window"), Description("The input dialog box title.")]
[Localizable(true), Bindable(true)]
public string Title { get ; set ; } = "" ;
/// <summary>Displays an input dialog in front of the specified object and with the specified prompt, caption, data, and image.</summary>
/// <param name="owner">An implementation of <see cref="IWin32Window"/> that will own the modal dialog box.</param>
/// <param name="prompt">The text prompt to display above all input options. This value can be <c>null</c>.</param>
/// <param name="caption">The caption for the dialog.</param>
/// <param name="data">
/// The data for the input. The data type will determine the type of input mechanism displayed. For simple types, a
/// <see cref="TextBox"/> with validation, or a <see cref="CheckBox"/> or a <see cref="ComboBox"/> will be displayed. For classes
/// and structures, all of the public, top-level, fields and properties will have input mechanisms shown for each. See Remarks for
/// more detail.
/// </param>
/// <param name="image">
/// The image to display on the top left corner of the dialog. This value can be <c>null</c> to display no image.
/// </param>
/// <param name="width">The desired width of the <see cref="InternalInputDialog"/>. A value of <c>0</c> indicates a default width.</param>
/// <returns>
/// Either <see cref="DialogResult.OK"/> or <see cref="DialogResult.Cancel"/>. On OK, the <paramref name="data"/> parameter will
/// include the updated values from the <see cref="InternalInputDialog"/>.
/// </returns>
/// <remarks></remarks>
2023-09-29 13:58:35 -04:00
public static DialogResult Show ( IWin32Window ? owner , string? prompt , string caption , ref object? data , Image ? image = null , int width = 0 )
2023-03-31 11:47:53 -04:00
{
using var dlg = new InternalInputDialog ( prompt , caption , image , data , width ) ;
var ret = owner = = null ? dlg . ShowDialog ( ) : dlg . ShowDialog ( owner ) ;
if ( ret = = DialogResult . OK )
data = dlg . Data ;
return ret ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Displays an input dialog with the specified prompt, caption, data, and image.</summary>
/// <param name="prompt">The text prompt to display above all input options. This value can be <c>null</c>.</param>
/// <param name="caption">The caption for the dialog.</param>
/// <param name="data">
/// The data for the input. The data type will determine the type of input mechanism displayed. For simple types, a
/// <see cref="TextBox"/> with validation, or a <see cref="CheckBox"/> or a <see cref="ComboBox"/> will be displayed. For classes
/// and structures, all of the public, top-level, fields and properties will have input mechanisms shown for each. See Remarks for
/// more detail.
/// </param>
/// <param name="image">
/// The image to display on the top left corner of the dialog. This value can be <c>null</c> to display no image.
/// </param>
/// <param name="width">The desired width of the <see cref="InternalInputDialog"/>. A value of <c>0</c> indicates a default width.</param>
/// <returns>
/// Either <see cref="DialogResult.OK"/> or <see cref="DialogResult.Cancel"/>. On OK, the <paramref name="data"/> parameter will
/// include the updated values from the <see cref="InternalInputDialog"/>.
/// </returns>
/// <remarks></remarks>
2023-09-29 13:58:35 -04:00
public static DialogResult Show ( string? prompt , string caption , ref object? data , Image ? image = null , int width = 0 ) = > Show ( null , prompt , caption , ref data , image , width ) ;
2023-03-31 11:47:53 -04:00
/// <summary>Resets all properties to their default values.</summary>
public override void Reset ( ) { }
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>
/// <para>This API supports the.NET Framework infrastructure and is not intended to be used directly from your code.</para>
/// <para>Specifies a common dialog box.</para>
/// </summary>
/// <param name="hwndOwner">A value that represents the window handle of the owner window for the common dialog box.</param>
/// <returns><c>true</c> if the data was collected; otherwise, <c>false</c>.</returns>
protected override bool RunDialog ( IntPtr hwndOwner ) = > Show ( NativeWindow . FromHandle ( hwndOwner ) , Prompt , Title , ref data , Image ) = = DialogResult . OK ;
2019-11-22 20:50:54 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Get input based on automatic interpretation of Data object.</summary>
internal class InternalInputDialog : Form
{
private const int prefWidth = 340 ;
2019-11-22 20:50:54 -05:00
2023-09-24 17:26:46 -04:00
private static readonly Dictionary < Type , char [ ] > keyPressValidChars = new ( )
2023-03-31 11:47:53 -04:00
{
[typeof(byte)] = GetCultureChars ( true , false , true ) ,
[typeof(sbyte)] = GetCultureChars ( true , true , true ) ,
[typeof(short)] = GetCultureChars ( true , true , true ) ,
[typeof(ushort)] = GetCultureChars ( true , false , true ) ,
[typeof(int)] = GetCultureChars ( true , true , true ) ,
[typeof(uint)] = GetCultureChars ( true , false , true ) ,
[typeof(long)] = GetCultureChars ( true , true , true ) ,
[typeof(ulong)] = GetCultureChars ( true , false , true ) ,
[typeof(double)] = GetCultureChars ( true , true , true , true , true , true ) ,
[typeof(float)] = GetCultureChars ( true , true , true , true , true , true ) ,
[typeof(decimal)] = GetCultureChars ( true , true , true , true , true ) ,
[typeof(TimeSpan)] = GetCultureChars ( true , true , false , new [ ] { '-' } ) ,
[typeof(Guid)] = GetCultureChars ( true , false , false , "-{}()" . ToCharArray ( ) ) ,
} ;
2023-09-24 17:26:46 -04:00
private static readonly Size minSize = new ( 193 , 104 ) ;
2023-03-31 11:47:53 -04:00
private static readonly Type [ ] simpleTypes = { typeof ( Enum ) , typeof ( decimal ) , typeof ( DateTime ) ,
typeof ( DateTimeOffset ) , typeof ( string ) , typeof ( TimeSpan ) , typeof ( Guid ) } ;
2023-09-24 17:26:46 -04:00
private static readonly Dictionary < Type , Predicate < string > > validations = new ( )
2023-03-31 11:47:53 -04:00
{
[typeof(byte)] = s = > byte . TryParse ( s , out var _ ) ,
[typeof(sbyte)] = s = > sbyte . TryParse ( s , out var _ ) ,
[typeof(short)] = s = > short . TryParse ( s , out var _ ) ,
[typeof(ushort)] = s = > ushort . TryParse ( s , out var _ ) ,
[typeof(int)] = s = > int . TryParse ( s , out var _ ) ,
[typeof(uint)] = s = > uint . TryParse ( s , out var _ ) ,
[typeof(long)] = s = > long . TryParse ( s , out var _ ) ,
[typeof(ulong)] = s = > ulong . TryParse ( s , out var _ ) ,
[typeof(char)] = s = > char . TryParse ( s , out var _ ) ,
[typeof(double)] = s = > double . TryParse ( s , out var _ ) ,
[typeof(float)] = s = > float . TryParse ( s , out var _ ) ,
[typeof(decimal)] = s = > decimal . TryParse ( s , out var _ ) ,
[typeof(DateTime)] = s = > DateTime . TryParse ( s , out var _ ) ,
[typeof(TimeSpan)] = s = > TimeSpan . TryParse ( s , out var _ ) ,
[typeof(Guid)] = s = > { try { var n = new Guid ( s ) ; return true ; } catch { return false ; } } ,
} ;
2023-09-29 13:58:35 -04:00
private readonly List < MemberInfo ? > items = new ( ) ;
2023-03-31 11:47:53 -04:00
private Panel borderPanel ;
private TableLayoutPanel buttonPanel ;
private Button cancelBtn ;
private IContainer components ;
2023-09-29 13:58:35 -04:00
private object? dataObj ;
2023-03-31 11:47:53 -04:00
private ErrorProvider errorProvider ;
2023-09-29 13:58:35 -04:00
private Image ? image ;
2023-03-31 11:47:53 -04:00
private Button okBtn ;
2023-09-29 13:58:35 -04:00
private string? prompt ;
2023-03-31 11:47:53 -04:00
private TableLayoutPanel table ;
/// <summary>Initializes a new instance of the <see cref="InternalInputDialog"/> class.</summary>
public InternalInputDialog ( ) = > InitializeComponent ( ) ;
2023-09-29 13:58:35 -04:00
internal InternalInputDialog ( string? prompt , string caption , Image ? image , object? data , int width ) : this ( )
2023-03-31 11:47:53 -04:00
{
Width = width ;
this . prompt = prompt ;
Text = caption ;
this . image = image ;
Data = data ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets the data.</summary>
/// <value>The data.</value>
[DefaultValue(null), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
2023-09-29 13:58:35 -04:00
public object? Data
2023-03-31 11:47:53 -04:00
{
get = > dataObj ;
set
2017-11-27 13:11:20 -05:00
{
2023-09-29 13:58:35 -04:00
value ? ? = string . Empty ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
items . Clear ( ) ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
if ( IsSimpleType ( value . GetType ( ) ) )
items . Add ( null ) ;
else
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
foreach ( var mi in value . GetType ( ) . GetMembers ( BindingFlags . Instance | BindingFlags . Public ) )
2017-11-27 13:11:20 -05:00
{
2023-09-29 13:58:35 -04:00
if ( InternalInputDialog . GetAttr ( mi ) ? . Hidden ? ? false )
2023-03-31 11:47:53 -04:00
continue ;
if ( mi is FieldInfo fi & & IsSupportedType ( fi . FieldType ) )
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
items . Add ( fi ) ;
}
else if ( mi is PropertyInfo pi & & IsSupportedType ( pi . PropertyType ) & & pi . GetIndexParameters ( ) . Length = = 0 & & pi . CanWrite )
{
items . Add ( pi ) ;
2017-11-27 13:11:20 -05:00
}
}
2023-09-29 13:58:35 -04:00
items . Sort ( ( x , y ) = > ( InternalInputDialog . GetAttr ( x ) ? . Order ? ? int . MaxValue ) - ( InternalInputDialog . GetAttr ( y ) ? . Order ? ? int . MaxValue ) ) ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
dataObj = value ;
BuildTable ( ) ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets the image.</summary>
/// <value>The image.</value>
[DefaultValue(null)]
2023-09-29 13:58:35 -04:00
public Image ? Image
2023-03-31 11:47:53 -04:00
{
get = > image ;
set
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
if ( image = = value ) return ;
image = value ;
BuildTable ( ) ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets the prompt.</summary>
/// <value>The prompt.</value>
[DefaultValue(null)]
2023-09-29 13:58:35 -04:00
public string? Prompt
2023-03-31 11:47:53 -04:00
{
get = > prompt ;
set
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
if ( prompt = = value ) return ;
prompt = value ;
BuildTable ( ) ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets the width of the control.</summary>
public new int Width
{
get = > base . Width ;
set
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
if ( value = = 0 ) value = prefWidth ;
value = Math . Max ( minSize . Width , value ) ;
MinimumSize = new Size ( value , minSize . Height ) ;
MaximumSize = new Size ( value , int . MaxValue ) ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private bool HasPrompt = > ! string . IsNullOrEmpty ( Prompt ) ;
2019-11-22 20:50:54 -05:00
2023-09-29 13:58:35 -04:00
private static object? ConvertFromStr ( string value , Type destType )
2023-03-31 11:47:53 -04:00
{
if ( destType = = typeof ( string ) )
return value ;
if ( value . Trim ( ) = = string . Empty )
return destType . IsValueType ? Activator . CreateInstance ( destType ) : null ;
if ( typeof ( IConvertible ) . IsAssignableFrom ( destType ) )
try { return Convert . ChangeType ( value , destType ) ; } catch { }
return TypeDescriptor . GetConverter ( destType ) . ConvertFrom ( value ) ;
}
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
private static string ConvertToStr ( object? value ) = > value switch
2023-03-31 11:47:53 -04:00
{
2023-09-29 13:58:35 -04:00
null = > string . Empty ,
IConvertible _ = > value ! . ToString ( ) ! ,
_ = > ( string ) TypeDescriptor . GetConverter ( value ) . ConvertTo ( value , typeof ( string ) ) ! ,
} ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private static int GetBestHeight ( Control c )
{
using var g = c . CreateGraphics ( ) ;
return TextRenderer . MeasureText ( g , c . Text , c . Font , new Size ( c . Width , 0 ) , TextFormatFlags . WordBreak ) . Height ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private static char [ ] GetCultureChars ( bool digits , bool neg , bool pos , bool dec = false , bool grp = false , bool e = false )
{
var c = System . Globalization . CultureInfo . CurrentCulture . NumberFormat ;
var l = new List < string > ( ) ;
if ( digits ) l . AddRange ( c . NativeDigits ) ;
if ( neg ) l . Add ( c . NegativeSign ) ;
if ( pos ) l . Add ( c . PositiveSign ) ;
if ( dec ) l . Add ( c . NumberDecimalSeparator ) ;
if ( grp ) l . Add ( c . NumberGroupSeparator ) ;
if ( e ) l . Add ( "Ee" ) ;
2023-09-29 13:58:35 -04:00
var sb = new StringBuilder ( ) ;
2023-03-31 11:47:53 -04:00
foreach ( var s in l )
sb . Append ( s ) ;
var ca = sb . ToString ( ) . ToCharArray ( ) ;
Array . Sort ( ca ) ;
return ca ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private static char [ ] GetCultureChars ( bool timeChars , bool timeSep , bool dateSep , char [ ] other )
{
var c = System . Globalization . CultureInfo . CurrentCulture ;
var l = new List < string > ( ) ;
if ( timeChars ) l . AddRange ( c . NumberFormat . NativeDigits ) ;
if ( timeSep ) { l . Add ( c . DateTimeFormat . TimeSeparator ) ; l . Add ( c . NumberFormat . NumberDecimalSeparator ) ; }
if ( dateSep ) l . Add ( c . DateTimeFormat . DateSeparator ) ;
if ( other ! = null & & other . Length > 0 ) l . Add ( new string ( other ) ) ;
2023-09-29 13:58:35 -04:00
var sb = new StringBuilder ( ) ;
2023-03-31 11:47:53 -04:00
foreach ( var s in l )
sb . Append ( s ) ;
var ca = sb . ToString ( ) . ToCharArray ( ) ;
Array . Sort ( ca ) ;
return ca ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private static bool IsSimpleType ( Type type ) = > type . IsPrimitive | | type . IsEnum | | simpleTypes . Contains ( type ) | | Convert . GetTypeCode ( type ) ! = TypeCode . Object | |
type . IsGenericType & & type . GetGenericTypeDefinition ( ) = = typeof ( Nullable < > ) & & IsSimpleType ( type . GetGenericArguments ( ) [ 0 ] ) ;
private static bool IsSupportedType ( Type type )
{
if ( typeof ( IConvertible ) . IsAssignableFrom ( type ) )
return true ;
var cvtr = TypeDescriptor . GetConverter ( type ) ;
return cvtr . CanConvertFrom ( typeof ( string ) ) & & cvtr . CanConvertTo ( typeof ( string ) ) ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Binds input text values back to the Data object.</summary>
private void BindToData ( )
{
for ( var i = 0 ; i < items . Count ; i + + )
2017-11-27 13:11:20 -05:00
{
var item = items [ i ] ;
var itemType = GetItemType ( item ) ;
2023-03-31 11:47:53 -04:00
// Get value from control
var c = table . Controls [ $"input{i}" ] ;
var box = c as CheckBox ;
2023-12-30 19:50:35 -05:00
var val = box ? . Checked ? ? ConvertFromStr ( c ! . Text , itemType ) ;
2023-03-31 11:47:53 -04:00
// Apply value to dataObj
2017-11-27 13:11:20 -05:00
if ( item = = null )
2023-03-31 11:47:53 -04:00
dataObj = val ;
2023-09-29 13:58:35 -04:00
else if ( item is PropertyInfo info )
info . SetValue ( dataObj , val , null ) ;
2017-11-27 13:11:20 -05:00
else
2023-03-31 11:47:53 -04:00
( ( FieldInfo ) item ) . SetValue ( dataObj , val ) ;
}
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private Control BuildInputForItem ( int i )
{
var item = items [ i ] ;
var itemType = GetItemType ( item ) ;
// Get default text value
2023-09-29 13:58:35 -04:00
object? val ;
2023-03-31 11:47:53 -04:00
if ( item = = null )
val = dataObj ;
2023-09-29 13:58:35 -04:00
else if ( item is PropertyInfo info )
val = info . GetValue ( dataObj , null ) ;
2023-03-31 11:47:53 -04:00
else
val = ( ( FieldInfo ) item ) . GetValue ( dataObj ) ;
var t = ConvertToStr ( val ) ;
// Build control type
Control retVal ;
if ( itemType = = typeof ( bool ) )
{
2023-09-29 13:58:35 -04:00
retVal = new CheckBox { AutoSize = true , Checked = ( bool ) val ! , Margin = new Padding ( 0 , 7 , 0 , 0 ) , MinimumSize = new Size ( 0 , 20 ) } ;
2023-03-31 11:47:53 -04:00
}
else if ( itemType . IsEnum )
{
var cb = new ComboBox { Dock = DockStyle . Fill , DropDownStyle = ComboBoxStyle . DropDownList } ;
cb . Items . AddRange ( Enum . GetNames ( itemType ) ) ;
cb . Text = t ;
retVal = cb ;
}
else
{
2023-09-29 13:58:35 -04:00
var tb = new TextBox { CausesValidation = true , Dock = DockStyle . Fill , Text = t , UseSystemPasswordChar = InternalInputDialog . GetAttr ( item ) ? . UsePasswordChar ? ? false } ;
2023-03-31 11:47:53 -04:00
tb . Enter + = ( s , e ) = > tb . SelectAll ( ) ;
if ( itemType = = typeof ( char ) )
tb . KeyPress + = ( s , e ) = > e . Handled = ! char . IsControl ( e . KeyChar ) & & tb . TextLength > 0 ;
2017-11-27 13:11:20 -05:00
else
2023-09-29 13:58:35 -04:00
tb . KeyPress + = ( s , e ) = > e . Handled = InternalInputDialog . IsInvalidKey ( e . KeyChar , itemType ) ;
2023-03-31 11:47:53 -04:00
tb . Validating + = ( s , e ) = >
2017-11-27 13:11:20 -05:00
{
2023-09-29 13:58:35 -04:00
var invalid = InternalInputDialog . TextIsInvalid ( tb , itemType ) ;
2023-03-31 11:47:53 -04:00
e . Cancel = invalid ;
errorProvider . SetError ( tb , invalid ? $"Text must be in a valid format for {itemType.Name}." : "" ) ;
} ;
tb . Validated + = ( s , e ) = > errorProvider . SetError ( tb , "" ) ;
errorProvider . SetIconPadding ( tb , - 18 ) ;
retVal = tb ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
// Set standard props
// TODO: Change out '7' for DPI specific spacing
retVal . Margin = new Padding ( items . Count = = 1 & & HasPrompt & & items [ 0 ] = = null ? 4 : 0 , 7 , 0 , 0 ) ;
retVal . Name = $"input{i}" ;
return retVal ;
}
private Label BuildLabelForItem ( int i )
{
var item = items [ i ] ;
var lbl = new Label { AutoSize = true , Dock = DockStyle . Left , Margin = new Padding ( 0 , 0 , 1 , 0 ) } ;
if ( item ! = null )
2017-11-27 13:11:20 -05:00
{
2023-09-29 13:58:35 -04:00
lbl . Text = ( InternalInputDialog . GetAttr ( item ) ? . Label ? ? item . Name ) + ":" ;
2023-03-31 11:47:53 -04:00
// TODO: Change out '10' for spacing needed to align label text with TextBox and '4' for DPI specific spacing
lbl . Margin = new Padding ( 0 , 10 , 4 , 0 ) ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
return lbl ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private void BuildTable ( )
{
table . SuspendLayout ( ) ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
// Clear out last layout
table . Controls . Clear ( ) ;
while ( table . RowStyles . Count > 1 )
table . RowStyles . RemoveAt ( 1 ) ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
table . RowCount = items . Count + ( HasPrompt ? 1 : 0 ) ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
// Icon
if ( Image ! = null )
{
table . Controls . Add ( new PictureBox { Image = Image , Size = Image . Size , Margin = new Padding ( 0 , 0 , 7 , 0 ) , TabStop = false } , 0 , 0 ) ;
2023-12-30 19:50:35 -05:00
table . SetRowSpan ( table . GetControlFromPosition ( 0 , 0 ) ! , table . RowCount ) ;
2023-03-31 11:47:53 -04:00
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
var hrow = 0 ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
// Add header row if needed
2023-09-29 13:58:35 -04:00
Label ? lbl = null ;
2023-03-31 11:47:53 -04:00
if ( HasPrompt )
{
lbl = new Label
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
AutoSize = true ,
Text = Prompt ,
Dock = DockStyle . Top ,
UseMnemonic = false ,
Font = new Font ( Font . FontFamily , Font . Size * 4 / 3 ) ,
ForeColor = Color . FromArgb ( 19 , 112 , 171 ) ,
Margin = new Padding ( items . Count = = 1 & & items [ 0 ] = = null ? 1 : 0 , 0 , 0 , 0 )
} ;
table . Controls . Add ( lbl , 1 , hrow + + ) ;
table . SetColumnSpan ( lbl , 2 ) ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
// Build rows for each item
for ( var i = 0 ; i < items . Count ; i + + )
{
if ( i + hrow > 0 )
table . RowStyles . Add ( new RowStyle ( SizeType . AutoSize ) ) ;
table . Controls . Add ( BuildLabelForItem ( i ) , 1 , i + hrow ) ;
table . Controls . Add ( BuildInputForItem ( i ) , 2 , i + hrow ) ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
table . ResumeLayout ( ) ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
if ( HasPrompt & & lbl ! = null & & lbl . PreferredWidth > lbl . Width )
lbl . MinimumSize = lbl . Size ;
}
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
private void CancelBtn_Click ( object? sender , EventArgs e ) = > Close ( ) ;
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
private static InputDialogItemAttribute ? GetAttr ( MemberInfo ? mi ) = > mi is null ? null : ( InputDialogItemAttribute ? ) Attribute . GetCustomAttribute ( mi , typeof ( InputDialogItemAttribute ) , true ) ;
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
private Type GetItemType ( MemberInfo ? mi ) = > mi = = null ? dataObj ! . GetType ( ) : ( ( mi as PropertyInfo ) ? . PropertyType ? ? ( ( FieldInfo ) mi ) . FieldType ) ;
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
[MemberNotNull(nameof(buttonPanel), nameof(cancelBtn), nameof(okBtn), nameof(borderPanel), nameof(table), nameof(components), nameof(errorProvider))]
2023-03-31 11:47:53 -04:00
private void InitializeComponent ( )
{
components = new Container ( ) ;
buttonPanel = new TableLayoutPanel ( ) ;
okBtn = new Button ( ) ;
cancelBtn = new Button ( ) ;
borderPanel = new Panel ( ) ;
table = new TableLayoutPanel ( ) ;
errorProvider = new ErrorProvider ( components ) ;
buttonPanel . SuspendLayout ( ) ;
( ( ISupportInitialize ) errorProvider ) . BeginInit ( ) ;
SuspendLayout ( ) ;
// buttonPanel
buttonPanel . AutoSize = true ;
buttonPanel . AutoSizeMode = AutoSizeMode . GrowAndShrink ;
buttonPanel . BackColor = SystemColors . Control ;
buttonPanel . ColumnCount = 3 ;
buttonPanel . ColumnStyles . Add ( new ColumnStyle ( SizeType . Percent , 100F ) ) ;
buttonPanel . ColumnStyles . Add ( new ColumnStyle ( ) ) ;
buttonPanel . ColumnStyles . Add ( new ColumnStyle ( ) ) ;
buttonPanel . Controls . Add ( okBtn , 1 , 0 ) ;
buttonPanel . Controls . Add ( cancelBtn , 2 , 0 ) ;
buttonPanel . Dock = DockStyle . Bottom ;
buttonPanel . Location = new Point ( 0 , 25 ) ;
buttonPanel . Margin = new Padding ( 0 ) ;
buttonPanel . MinimumSize = new Size ( 177 , 40 ) ;
buttonPanel . Name = "buttonPanel" ;
buttonPanel . Padding = new Padding ( 10 , 8 , 10 , 9 ) ;
buttonPanel . RowCount = 1 ;
buttonPanel . RowStyles . Add ( new RowStyle ( ) ) ;
buttonPanel . Size = new Size ( 177 , 40 ) ;
buttonPanel . TabIndex = 1 ;
// okBtn
okBtn . Location = new Point ( 10 , 8 ) ;
okBtn . Margin = new Padding ( 0 , 0 , 7 , 0 ) ;
okBtn . MinimumSize = new Size ( 75 , 23 ) ;
okBtn . Name = "okBtn" ;
okBtn . Size = new Size ( 75 , 23 ) ;
okBtn . TabIndex = 1 ;
okBtn . Text = "OK" ;
okBtn . UseVisualStyleBackColor = true ;
okBtn . Click + = new EventHandler ( OkBtn_Click ) ;
// cancelBtn
cancelBtn . DialogResult = DialogResult . Cancel ;
cancelBtn . Location = new Point ( 92 , 8 ) ;
cancelBtn . Margin = new Padding ( 0 ) ;
cancelBtn . MinimumSize = new Size ( 75 , 23 ) ;
cancelBtn . Name = "cancelBtn" ;
cancelBtn . Size = new Size ( 75 , 23 ) ;
cancelBtn . TabIndex = 2 ;
cancelBtn . Text = "&Cancel" ;
cancelBtn . UseVisualStyleBackColor = true ;
cancelBtn . Click + = new EventHandler ( CancelBtn_Click ) ;
// borderPanel
borderPanel . BackColor = Color . FromArgb ( 223 , 223 , 223 ) ;
borderPanel . Dock = DockStyle . Bottom ;
borderPanel . Location = new Point ( 0 , 24 ) ;
borderPanel . Margin = new Padding ( 0 ) ;
borderPanel . MinimumSize = new Size ( 0 , 1 ) ;
borderPanel . Name = "borderPanel" ;
borderPanel . Size = new Size ( 177 , 1 ) ;
borderPanel . TabIndex = 3 ;
// table
table . AutoSize = true ;
table . AutoSizeMode = AutoSizeMode . GrowAndShrink ;
table . ColumnCount = 3 ;
table . ColumnStyles . Add ( new ColumnStyle ( ) ) ;
table . ColumnStyles . Add ( new ColumnStyle ( ) ) ;
table . ColumnStyles . Add ( new ColumnStyle ( SizeType . Percent , 100F ) ) ;
table . Dock = DockStyle . Fill ;
table . Location = new Point ( 0 , 0 ) ;
table . Margin = new Padding ( 0 ) ;
table . Name = "table" ;
table . Padding = new Padding ( 10 ) ;
table . RowCount = 1 ;
table . RowStyles . Add ( new RowStyle ( ) ) ;
table . Size = new Size ( 177 , 24 ) ;
table . TabIndex = 0 ;
// errorProvider
errorProvider . ContainerControl = this ;
// InternalInputDialog
AcceptButton = okBtn ;
AutoSize = true ;
AutoSizeMode = AutoSizeMode . GrowAndShrink ;
BackColor = SystemColors . Window ;
CancelButton = cancelBtn ;
ClientSize = new Size ( prefWidth , 65 ) ;
Controls . Add ( table ) ;
Controls . Add ( borderPanel ) ;
Controls . Add ( buttonPanel ) ;
Font = new Font ( "Segoe UI" , 9F , FontStyle . Regular , GraphicsUnit . Point , 0 ) ;
FormBorderStyle = FormBorderStyle . FixedDialog ;
MinimumSize = new Size ( prefWidth , minSize . Height ) ;
MaximumSize = new Size ( prefWidth , int . MaxValue ) ;
Name = "InternalInputDialog" ;
StartPosition = FormStartPosition . CenterParent ;
buttonPanel . ResumeLayout ( false ) ;
( ( ISupportInitialize ) errorProvider ) . EndInit ( ) ;
ResumeLayout ( false ) ;
PerformLayout ( ) ;
}
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
private static bool IsInvalidKey ( char keyChar , Type itemType )
2023-03-31 11:47:53 -04:00
{
if ( char . IsControl ( keyChar ) )
2017-11-27 13:11:20 -05:00
return false ;
2023-03-31 11:47:53 -04:00
keyPressValidChars . TryGetValue ( itemType , out var chars ) ;
if ( chars ! = null )
2017-11-27 13:11:20 -05:00
{
2023-03-31 11:47:53 -04:00
var si = Array . BinarySearch ( chars , keyChar ) ;
System . Diagnostics . Debug . WriteLine ( $"Processed key {keyChar} as {si} position." ) ;
if ( si < 0 )
return true ;
2017-11-27 13:11:20 -05:00
}
2023-03-31 11:47:53 -04:00
return false ;
}
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
private void OkBtn_Click ( object? sender , EventArgs e )
2023-03-31 11:47:53 -04:00
{
BindToData ( ) ;
DialogResult = DialogResult . OK ;
Close ( ) ;
}
2017-11-27 13:11:20 -05:00
2023-09-29 13:58:35 -04:00
private static bool TextIsInvalid ( TextBox tb , Type itemType )
2023-03-31 11:47:53 -04:00
{
if ( string . IsNullOrEmpty ( tb . Text ) )
return false ;
validations . TryGetValue ( itemType , out var p ) ;
return p ! = null & & ! p ( tb . Text ) ;
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
private class RegexTextBox : TextBox
{
2023-09-29 13:58:35 -04:00
public string? RegexPattern { get ; set ; }
2023-03-31 11:47:53 -04:00
protected override void OnKeyPress ( KeyPressEventArgs e ) = >
//System.Text.RegularExpressions.Regex.IsMatch()
base . OnKeyPress ( e ) ;
2017-11-27 13:11:20 -05:00
}
}
2023-03-31 11:47:53 -04:00
}
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>
/// Allows a developer to attribute a property or field with text that gets shown instead of the field or property name in an <see cref="InputDialog"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class InputDialogItemAttribute : Attribute
{
/// <summary>Initializes a new instance of the <see cref="InputDialogItemAttribute"/> class.</summary>
public InputDialogItemAttribute ( ) { }
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Initializes a new instance of the <see cref="InputDialogItemAttribute"/> class.</summary>
/// <param name="label">The label to use in the <see cref="InputDialog"/> as the label for this field or property.</param>
public InputDialogItemAttribute ( string label ) = > Label = label ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets a value indicating whether this item is hidden and not displayed by the <see cref="InputDialog"/>.</summary>
/// <value><c>true</c> if hidden; otherwise, <c>false</c>.</value>
public bool Hidden { get ; set ; } = false ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets the label to use in the <see cref="InputDialog"/> as the label for this field or property.</summary>
/// <value>The label for this item.</value>
2023-09-29 13:58:35 -04:00
public string Label { get ; } = "" ;
2017-11-27 13:11:20 -05:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets the order in which to display the input for this field or property within the <see cref="InputDialog"/>.</summary>
/// <value>The display order for this item.</value>
public int Order { get ; set ; } = int . MaxValue ;
2018-03-26 15:21:29 -04:00
2023-03-31 11:47:53 -04:00
/// <summary>Gets or sets a value indicating whether text boxes should display text using the system default password character.</summary>
/// <value><c>true</c> if using a password character; otherwise, <c>false</c>.</value>
public bool UsePasswordChar { get ; set ; } = false ;
2017-11-27 13:11:20 -05:00
}