docfx/docs/prompt.md
Prompt<TView, TResult> enables any View to be shown in a modal dialog with Ok/Cancel buttons and typed result extraction. This provides a flexible, type-safe way to prompt users for input using any Terminal.Gui view.
TResult parameter provides compile-time type safetyTResult is string, automatically uses View.Text if no extractor providedIRunnable and IApplication for convenient usagebeginInitHandler callbackclassDiagram
Dialog~TResult~ <|-- Prompt~TView,TResult~
IRunnable~TResult~ <|.. Prompt~TView,TResult~
class Dialog~TResult~ {
+TResult? Result
+Button[] Buttons
+OnAccepting(CommandEventArgs) bool
}
class Prompt~TView,TResult~ {
-TView _wrappedView
+Func~TView,TResult?~? ResultExtractor
+GetWrappedView() TView
#OnAccepting(CommandEventArgs) bool
}
note for Prompt~TView,TResult~ "Auto-uses View.Text when\nTResult is string and\nResultExtractor is null"
// From within a Window or other Runnable
DateTime? date = this.Prompt<DatePicker, DateTime> (
resultExtractor: dp => dp.Date,
beginInitHandler: prompt =>
{
prompt.Title = "Select Date";
prompt.GetWrappedView ().Date = DateTime.Now;
});
if (date is { } selectedDate)
{
MessageBox.Query ("Success", $"You selected: {selectedDate:yyyy-MM-dd}", Strings.btnOk);
}
When TResult is string, you can omit the resultExtractor:
// Auto-uses ColorPicker.Text (returns color name/value)
string? colorText = mainWindow.Prompt<ColorPicker, string> (
beginInitHandler: prompt =>
{
prompt.Title = "Pick a Color";
prompt.GetWrappedView ().SelectedColor = Color.Blue;
});
if (colorText is { })
{
MessageBox.Query ("Color", $"You selected: {colorText}", Strings.btnOk);
}
# Create and configure view
$textField = [TextField]::new()
$textField.Text = "Enter your name"
# Returns view.Text or null
$result = $app.Prompt($textField)
if ($result) {
Write-Output "User entered: $result"
}
# For non-text results, use the generic method
$datePicker = [DatePicker]::new()
$datePicker.Date = [DateTime]::Now
$result = $mainWindow.Prompt[DatePicker,DateTime](
$datePicker,
[Func[DatePicker,DateTime]] { param($dp) return $dp.Date })
if ($result) {
Write-Output $result.ToString("yyyy-MM-dd")
}
public static TResult? Prompt<TView, TResult> (
this IRunnable host,
TView? view = null,
Func<TView, TResult?>? resultExtractor = null,
TResult? input = default,
Action<Prompt<TView, TResult>>? beginInitHandler = null)
where TView : View, new()
Parameters:
view: View instance to wrap, or null to auto-createresultExtractor: Function to extract result from view, or null for auto-Text (when TResult is string)input: Initial value (reserved for future IValue<T> pattern)beginInitHandler: Callback to customize dialog before displayReturns: TResult? - extracted result or null if canceled
Auto-Text Fallback: If resultExtractor is null and TResult is string, automatically returns view.Text.
public static string? Prompt<TView> (
this IApplication app,
TView view)
where TView : View, new()
Parameters:
view: View instance to displayReturns: string? - view.Text or null if canceled
Note: For views where .Text is not meaningful (e.g., ListView with multi-select), use the generic IRunnable.Prompt method with a custom result extractor.
Most common pattern - create and configure view, then prompt:
ColorPicker colorPicker = new ()
{
SelectedColor = Color.Red,
Style = new () { ShowColorName = true, ShowTextFields = true }
};
Color? result = mainWindow.Prompt<ColorPicker, Color> (
view: colorPicker,
resultExtractor: cp => cp.SelectedColor,
beginInitHandler: prompt => prompt.Title = "Pick Color");
Let Prompt create the view, customize via handler:
DateTime? date = mainWindow.Prompt<DatePicker, DateTime> (
resultExtractor: dp => dp.Date,
beginInitHandler: prompt =>
{
prompt.Title = "Select Date";
prompt.GetWrappedView ().Date = DateTime.Now;
prompt.GetWrappedView ().Format = "yyyy-MM-dd";
});
When you want the string representation:
// Get date as formatted string instead of DateTime
string? dateText = mainWindow.Prompt<DatePicker, string> (
beginInitHandler: prompt =>
{
prompt.Title = "Select Date";
prompt.GetWrappedView ().Date = DateTime.Now;
prompt.GetWrappedView ().Format = "MM/dd/yyyy";
});
Create Prompt directly when you need full control:
TextField textField = new () { Width = Dim.Fill () };
using Prompt<TextField, string> prompt = new (textField)
{
Title = "Enter Name",
ResultExtractor = tf => tf.Text
};
// Customize via events
prompt.Initialized += (s, e) =>
{
prompt.BorderStyle = LineStyle.Rounded;
prompt.Buttons [0].Text = "Cancel";
prompt.Buttons [1].Text = "Submit";
};
string? result = app.Run (prompt);
Many views provide meaningful .Text implementations that enable automatic text extraction:
| View | Text Behavior |
|---|---|
TextField | User input text |
TextView | Multiline text content |
DatePicker | Formatted date string (e.g., "1/15/2026") |
ColorPicker | Color name or representation (e.g., "Red", "Color [A=255, R=255, G=0, B=0]") |
Label | Label text |
Button | Button text |
Views where .Text is not meaningful:
ListView (especially with multi-select) - use custom resultExtractorTreeView - use custom resultExtractor.Text override - use custom resultExtractorUse beginInitHandler to customize dialog properties:
Color? result = mainWindow.Prompt<ColorPicker, Color> (
resultExtractor: cp => cp.SelectedColor,
beginInitHandler: prompt =>
{
// Dialog properties
prompt.Title = "Choose Color";
prompt.BorderStyle = LineStyle.Rounded;
prompt.Width = Dim.Percent (60);
prompt.Height = Dim.Percent (50);
// Wrapped view properties
prompt.GetWrappedView ().SelectedColor = Color.Blue;
prompt.GetWrappedView ().Width = Dim.Fill ();
});
Buttons are added in constructor. Customize via Initialized event:
DateTime? date = mainWindow.Prompt<DatePicker, DateTime> (
resultExtractor: dp => dp.Date,
beginInitHandler: prompt =>
{
prompt.Title = "Select Date";
prompt.Initialized += (s, e) =>
{
// Buttons[0] is Cancel, Buttons[1] is Ok
prompt.Buttons [0].Text = "Nope";
prompt.Buttons [1].Text = "Yep";
};
});
The OnAccepting method uses this logic:
protected override bool OnAccepting (CommandEventArgs args)
{
if (base.OnAccepting (args)) return true;
if (ResultExtractor is { })
{
// 1. Use explicit extractor if provided
Result = ResultExtractor (GetWrappedView ());
}
else if (typeof(TResult) == typeof(string))
{
// 2. Auto-fallback to .Text when TResult is string
Result = (TResult?)(object?)GetWrappedView ().Text;
}
// 3. Otherwise Result remains null
return false;
}
Priority:
ResultExtractor - always used if provided.Text extraction - when TResult is string and no extractorTResult is not stringFor simple text input, the IApplication.Prompt method is ideal:
$textField = [TextField]::new()
$textField.Text = "Default value"
$result = $app.Prompt($textField) # Returns string? from view.Text
For non-text results, PowerShell users have two options:
Option 1: Use generic IRunnable method (requires Func delegate):
$result = $mainWindow.Prompt[DatePicker,DateTime](
$datePicker,
[Func[DatePicker,DateTime]] { param($dp) return $dp.Date })
Option 2: Create Prompt directly (more verbose but clearer):
$prompt = [Prompt[DatePicker,DateTime]]::new($datePicker)
$prompt.Title = "Select Date"
$prompt.ResultExtractor = [Func[DatePicker,DateTime]] {
param($dp)
return $dp.Date
}
$app.Run($prompt)
$result = $prompt.Result
Always use nullable result types to distinguish cancellation from valid results:
// GOOD - nullable type
DateTime? date = mainWindow.Prompt<DatePicker, DateTime?> (...);
if (date is null)
{
// User canceled
}
else
{
// User accepted with value
}
// BAD - non-nullable value type
DateTime date = mainWindow.Prompt<DatePicker, DateTime> (...);
// Returns default(DateTime) on cancel - can't distinguish from actual default value!
Views that implement IValue<T> provide automatic result extraction without requiring an explicit resultExtractor. This is the recommended pattern for views that have a primary value.
public interface IValue<TValue>
{
TValue? Value { get; set; }
event EventHandler<ValueChangingEventArgs<TValue?>>? ValueChanging;
event EventHandler<ValueChangedEventArgs<TValue?>>? ValueChanged;
}
The interface follows the Cancellable Work Pattern (CWP):
ValueChanging fires before the value changes; set Handled = true to cancelValueChanged fires after the value has changedWhen the wrapped view implements IValue<TResult>, Prompt automatically extracts the result:
// No resultExtractor needed - AttributePicker implements IValue<Attribute?>
Attribute? attr = mainWindow.Prompt<AttributePicker, Attribute?> (
beginInitHandler: prompt =>
{
prompt.Title = "Select Text Attribute";
prompt.GetWrappedView ().Value = new Attribute (Color.White, Color.Blue);
});
To make your custom view work seamlessly with Prompt, implement IValue<T> using CWPPropertyHelper.ChangeProperty:
public class MyRatingView : View, IValue<int?>
{
private int? _value;
public int? Value
{
get => _value;
set => CWPPropertyHelper.ChangeProperty (
this,
ref _value,
value,
OnValueChanging,
ValueChanging,
newValue =>
{
_value = newValue;
UpdateStarDisplay ();
},
OnValueChanged,
ValueChanged,
out _);
}
public event EventHandler<ValueChangingEventArgs<int?>>? ValueChanging;
public event EventHandler<ValueChangedEventArgs<int?>>? ValueChanged;
protected virtual bool OnValueChanging (ValueChangingEventArgs<int?> args) => false;
protected virtual void OnValueChanged (ValueChangedEventArgs<int?> args) { }
}
Now your view works with Prompt without a custom extractor:
int? rating = mainWindow.Prompt<MyRatingView, int?> (
beginInitHandler: prompt => prompt.Title = "Rate this item");
AttributePicker is a composite view that demonstrates the IValue<T> pattern. It allows users to select a complete Attribute (foreground color, background color, and text style).
// Simple - IValue<Attribute?> provides automatic extraction
Attribute? result = mainWindow.Prompt<AttributePicker, Attribute?> (
beginInitHandler: prompt =>
{
prompt.Title = "Choose Text Style";
prompt.GetWrappedView ().SampleText = "Preview your selection";
});
if (result.HasValue)
{
myTextView.SetAttribute (result.Value);
}
AttributePicker picker = new ()
{
Value = existingAttribute,
SampleText = "Sample Text Preview"
};
// Validate changes
picker.ValueChanging += (_, e) =>
{
// Prevent identical foreground/background
if (e.NewValue?.Foreground == e.NewValue?.Background)
{
e.Handled = true;
}
};
Attribute? result = mainWindow.Prompt<AttributePicker, Attribute?> (
view: picker,
beginInitHandler: prompt => prompt.Title = "Edit Attribute");
The host parameter in IRunnable.Prompt captures the hosting relationship. Future versions will use this for relative positioning:
// Future: Dialog positioned relative to mainWindow, not just centered on screen
DateTime? date = mainWindow.Prompt<DatePicker, DateTime> (...);