mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Initial plan * Add comprehensive MouseClick removal analysis Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix code review feedback on analysis document Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add actual line numbers to analysis document Co-authored-by: tig <585482+tig@users.noreply.github.com> * Final polish on analysis document - clarify TextView Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 3: Port 6 simple Views from MouseClick to Selecting/MouseBindings Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 2: Port 10 Scenarios from MouseClick to Selecting/remove Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update analysis document - Phases 2 & 3 complete Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix failing tests - Button multi-button support and Label focus handling Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 4: Port ScrollBar from OnMouseClick to OnSelecting; remove analysis doc Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 5: Update/remove tests that explicitly test MouseClick API Co-authored-by: tig <585482+tig@users.noreply.github.com> * Code cleanup * Remove MouseClick event Consolidated mouse event handling by removing the `MouseClick` event and integrating its functionality into the `MouseEvent` event. Simplified `MouseEventArgs` documentation and added support for invoking commands bound to mouse events. Reorganized code by removing `Mouse Pressed Events` and `Mouse Click Events` regions, introducing a new `WhenGrabbed Handlers` region. Updated tests to replace `MouseClick` with `MouseEvent`, adjusted test logic, and improved variable naming for clarity. Removed redundant assertions and unused code related to `MouseClick`. Improved event propagation logic to ensure proper handling of unhandled events. Performed general code cleanup to enhance readability and maintainability. * Updated deep dives. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig <tig@users.noreply.github.com>
1218 lines
24 KiB
Markdown
1218 lines
24 KiB
Markdown
# Migrating From v1 To v2
|
|
|
|
This document provides a comprehensive guide for migrating applications from Terminal.Gui v1 to v2.
|
|
|
|
For detailed breaking change documentation, check out this Discussion: https://github.com/gui-cs/Terminal.Gui/discussions/2448
|
|
|
|
## Table of Contents
|
|
|
|
- [Overview of Major Changes](#overview-of-major-changes)
|
|
- [Application Architecture](#application-architecture)
|
|
- [View Construction and Initialization](#view-construction-and-initialization)
|
|
- [Layout System Changes](#layout-system-changes)
|
|
- [Color and Attribute Changes](#color-and-attribute-changes)
|
|
- [Type Changes](#type-changes)
|
|
- [Unicode and Text](#unicode-and-text)
|
|
- [Keyboard API](#keyboard-api)
|
|
- [Mouse API](#mouse-api)
|
|
- [Navigation Changes](#navigation-changes)
|
|
- [Scrolling Changes](#scrolling-changes)
|
|
- [Adornments](#adornments)
|
|
- [Event Pattern Changes](#event-pattern-changes)
|
|
- [View-Specific Changes](#view-specific-changes)
|
|
- [Disposal and Resource Management](#disposal-and-resource-management)
|
|
- [API Terminology Changes](#api-terminology-changes)
|
|
|
|
---
|
|
|
|
## Overview of Major Changes
|
|
|
|
Terminal.Gui v2 represents a major architectural evolution with these key improvements:
|
|
|
|
1. **Instance-Based Application Model** - Move from static `Application` to `IApplication` instances
|
|
2. **IRunnable Architecture** - Interface-based runnable pattern with type-safe results
|
|
3. **Simplified Layout** - Removed Absolute/Computed distinction, improved adornments
|
|
4. **24-bit TrueColor** - Full color support by default
|
|
5. **Enhanced Input** - Better keyboard and mouse APIs
|
|
6. **Built-in Scrolling** - All views support scrolling inherently
|
|
7. **Fluent API** - Method chaining for elegant code
|
|
8. **Proper Disposal** - IDisposable pattern throughout
|
|
|
|
---
|
|
|
|
## Application Architecture
|
|
|
|
### Instance-Based Application Model
|
|
|
|
**v1 Pattern (Static):**
|
|
```csharp
|
|
// v1 - static Application
|
|
Application.Init();
|
|
var top = Application.Top;
|
|
top.Add(myView);
|
|
Application.Run();
|
|
Application.Shutdown();
|
|
```
|
|
|
|
**v2 Recommended Pattern (Instance-Based):**
|
|
```csharp
|
|
// v2 - instance-based with using statement
|
|
using (var app = Application.Create().Init())
|
|
{
|
|
var top = new Window();
|
|
top.Add(myView);
|
|
app.Run(top);
|
|
top.Dispose();
|
|
} // app.Dispose() called automatically
|
|
```
|
|
|
|
**v2 Legacy Pattern (Still Works):**
|
|
```csharp
|
|
// v2 - legacy static (marked obsolete but functional)
|
|
Application.Init();
|
|
var top = new Window();
|
|
top.Add(myView);
|
|
Application.Run(top);
|
|
top.Dispose();
|
|
Application.Shutdown(); // Obsolete - use Dispose() instead
|
|
```
|
|
|
|
### IRunnable Architecture
|
|
|
|
v2 introduces `IRunnable<TResult>` for type-safe, runnable views:
|
|
|
|
```csharp
|
|
// Create a dialog that returns a typed result
|
|
public class FileDialog : Runnable<string?>
|
|
{
|
|
private TextField _pathField;
|
|
|
|
public FileDialog()
|
|
{
|
|
Title = "Select File";
|
|
_pathField = new TextField { Width = Dim.Fill() };
|
|
Add(_pathField);
|
|
|
|
var okButton = new Button { Text = "OK", IsDefault = true };
|
|
okButton.Accepting += (s, e) => {
|
|
Result = _pathField.Text;
|
|
Application.RequestStop();
|
|
};
|
|
AddButton(okButton);
|
|
}
|
|
|
|
protected override bool OnIsRunningChanging(bool oldValue, bool newValue)
|
|
{
|
|
if (!newValue) // Stopping - extract result before disposal
|
|
{
|
|
Result = _pathField?.Text;
|
|
}
|
|
return base.OnIsRunningChanging(oldValue, newValue);
|
|
}
|
|
}
|
|
|
|
// Use with fluent API
|
|
using (var app = Application.Create().Init())
|
|
{
|
|
app.Run<FileDialog>();
|
|
string? result = app.GetResult<string>();
|
|
|
|
if (result is { })
|
|
{
|
|
OpenFile(result);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key Benefits:**
|
|
- Type-safe results (no casting)
|
|
- Automatic disposal of framework-created runnables
|
|
- CWP-compliant lifecycle events
|
|
- Works with any View (not just Toplevel)
|
|
|
|
### Disposal and Resource Management
|
|
|
|
v2 requires explicit disposal:
|
|
|
|
```csharp
|
|
// ❌ v1 - Application.Shutdown() disposed everything
|
|
Application.Init();
|
|
var top = new Window();
|
|
Application.Run(top);
|
|
Application.Shutdown(); // Disposed top automatically
|
|
|
|
// ✅ v2 - Dispose views explicitly
|
|
using (var app = Application.Create().Init())
|
|
{
|
|
var top = new Window();
|
|
app.Run(top);
|
|
top.Dispose(); // Must dispose
|
|
}
|
|
|
|
// ✅ v2 - Framework-created runnables disposed automatically
|
|
using (var app = Application.Create().Init())
|
|
{
|
|
app.Run<MyDialog>(); // Dialog disposed automatically
|
|
var result = app.GetResult<MyResult>();
|
|
}
|
|
```
|
|
|
|
**Disposal Rules:**
|
|
- "Whoever creates it, owns it"
|
|
- `Run<TRunnable>()`: Framework creates → Framework disposes
|
|
- `Run(IRunnable)`: Caller creates → Caller disposes
|
|
- Always dispose `IApplication` (use `using` statement)
|
|
|
|
### View.App Property
|
|
|
|
Views now have an `App` property for accessing the application context:
|
|
|
|
```csharp
|
|
// ❌ v1 - Direct static reference
|
|
Application.Driver.Move(x, y);
|
|
|
|
// ✅ v2 - Use View.App
|
|
App?.Driver.Move(x, y);
|
|
|
|
// ✅ v2 - Dependency injection
|
|
public class MyView : View
|
|
{
|
|
private readonly IApplication _app;
|
|
|
|
public MyView(IApplication app)
|
|
{
|
|
_app = app;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## View Construction and Initialization
|
|
|
|
### Constructors → Initializers
|
|
|
|
**v1:**
|
|
```csharp
|
|
var myView = new View(new Rect(10, 10, 40, 10));
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
var myView = new View
|
|
{
|
|
X = 10,
|
|
Y = 10,
|
|
Width = 40,
|
|
Height = 10
|
|
};
|
|
```
|
|
|
|
### Initialization Pattern
|
|
|
|
v2 uses `ISupportInitializeNotification`:
|
|
|
|
```csharp
|
|
// v1 - No explicit initialization
|
|
var view = new View();
|
|
Application.Run(view);
|
|
|
|
// v2 - Automatic initialization via BeginInit/EndInit
|
|
var view = new View();
|
|
// BeginInit() called automatically when added to SuperView
|
|
// EndInit() called automatically
|
|
// Initialized event raised after EndInit()
|
|
```
|
|
|
|
---
|
|
|
|
## Layout System Changes
|
|
|
|
### Removed LayoutStyle Distinction
|
|
|
|
v1 had `Absolute` and `Computed` layout styles. v2 removed this distinction.
|
|
|
|
**v1:**
|
|
```csharp
|
|
view.LayoutStyle = LayoutStyle.Computed;
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// No LayoutStyle - all layout is declarative via Pos/Dim
|
|
view.X = Pos.Center();
|
|
view.Y = Pos.Center();
|
|
view.Width = Dim.Percent(50);
|
|
view.Height = Dim.Fill();
|
|
```
|
|
|
|
### Frame vs Bounds
|
|
|
|
**v1:**
|
|
- `Frame` - Position/size in SuperView coordinates
|
|
- `Bounds` - Always `{0, 0, Width, Height}` (location always empty)
|
|
|
|
**v2:**
|
|
- `Frame` - Position/size in SuperView coordinates (same as v1)
|
|
- `Viewport` - Visible area in content coordinates (replaces Bounds)
|
|
- **Important**: `Viewport.Location` can now be non-zero for scrolling
|
|
|
|
```csharp
|
|
// ❌ v1
|
|
var size = view.Bounds.Size;
|
|
Debug.Assert(view.Bounds.Location == Point.Empty); // Always true
|
|
|
|
// ✅ v2
|
|
var visibleArea = view.Viewport;
|
|
var contentSize = view.GetContentSize();
|
|
|
|
// Viewport.Location can be non-zero when scrolled
|
|
view.ScrollVertical(10);
|
|
Debug.Assert(view.Viewport.Location.Y == 10);
|
|
```
|
|
|
|
### Pos and Dim API Changes
|
|
|
|
| v1 | v2 |
|
|
|----|-----|
|
|
| `Pos.At(x)` | `Pos.Absolute(x)` |
|
|
| `Dim.Sized(width)` | `Dim.Absolute(width)` |
|
|
| `Pos.Anchor()` | `Pos.GetAnchor()` |
|
|
| `Dim.Anchor()` | `Dim.GetAnchor()` |
|
|
|
|
```csharp
|
|
// ❌ v1
|
|
view.X = Pos.At(10);
|
|
view.Width = Dim.Sized(20);
|
|
|
|
// ✅ v2
|
|
view.X = Pos.Absolute(10);
|
|
view.Width = Dim.Absolute(20);
|
|
```
|
|
|
|
### View.AutoSize Removed
|
|
|
|
**v1:**
|
|
```csharp
|
|
view.AutoSize = true;
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
view.Width = Dim.Auto();
|
|
view.Height = Dim.Auto();
|
|
```
|
|
|
|
See [Dim.Auto Deep Dive](dimauto.md) for details.
|
|
|
|
---
|
|
|
|
## Adornments
|
|
|
|
v2 adds `Border`, `Margin`, and `Padding` as built-in adornments.
|
|
|
|
**v1:**
|
|
```csharp
|
|
// Custom border drawing
|
|
view.Border = new Border { /* ... */ };
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Built-in Border adornment
|
|
view.BorderStyle = LineStyle.Single;
|
|
view.Border.Thickness = new Thickness(1);
|
|
view.Title = "My View";
|
|
|
|
// Built-in Margin and Padding
|
|
view.Margin.Thickness = new Thickness(2);
|
|
view.Padding.Thickness = new Thickness(1);
|
|
```
|
|
|
|
See [Layout Deep Dive](layout.md) for complete details.
|
|
|
|
---
|
|
|
|
## Color and Attribute Changes
|
|
|
|
### 24-bit TrueColor Default
|
|
|
|
v2 uses 24-bit color by default.
|
|
|
|
```csharp
|
|
// v1 - Limited color palette
|
|
var color = Color.Brown;
|
|
|
|
// v2 - ANSI-compliant names + TrueColor
|
|
var color = Color.Yellow; // Brown renamed
|
|
var customColor = new Color(0xFF, 0x99, 0x00); // 24-bit RGB
|
|
```
|
|
|
|
### Attribute.Make Removed
|
|
|
|
**v1:**
|
|
```csharp
|
|
var attr = Attribute.Make(Color.BrightMagenta, Color.Blue);
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
var attr = new Attribute(Color.BrightMagenta, Color.Blue);
|
|
```
|
|
|
|
### Color Name Changes
|
|
|
|
| v1 | v2 |
|
|
|----|-----|
|
|
| `Color.Brown` | `Color.Yellow` |
|
|
|
|
---
|
|
|
|
## Type Changes
|
|
|
|
### Low-Level Types
|
|
|
|
| v1 | v2 |
|
|
|----|-----|
|
|
| `Rect` | `Rectangle` |
|
|
| `Point` | `Point` |
|
|
| `Size` | `Size` |
|
|
|
|
```csharp
|
|
// ❌ v1
|
|
Rect rect = new Rect(0, 0, 10, 10);
|
|
|
|
// ✅ v2
|
|
Rectangle rect = new Rectangle(0, 0, 10, 10);
|
|
```
|
|
|
|
---
|
|
|
|
## Unicode and Text
|
|
|
|
### NStack.ustring Removed
|
|
|
|
**v1:**
|
|
```csharp
|
|
using NStack;
|
|
ustring text = "Hello";
|
|
var width = text.Sum(c => Rune.ColumnWidth(c));
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
using System.Text;
|
|
string text = "Hello";
|
|
var width = text.GetColumns(); // Extension method
|
|
```
|
|
|
|
### Rune Changes
|
|
|
|
**v1:**
|
|
```csharp
|
|
// Implicit cast
|
|
myView.AddRune(col, row, '▄');
|
|
|
|
// Width
|
|
var width = Rune.ColumnWidth(rune);
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Explicit constructor
|
|
myView.AddRune(col, row, new Rune('▄'));
|
|
|
|
// Width
|
|
var width = rune.GetColumns();
|
|
```
|
|
|
|
See [Unicode](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#unicode) for details.
|
|
|
|
---
|
|
|
|
## Keyboard API
|
|
|
|
v2 has a completely redesigned keyboard API.
|
|
|
|
### Key Class
|
|
|
|
**v1:**
|
|
```csharp
|
|
KeyEvent keyEvent;
|
|
if (keyEvent.KeyCode == KeyCode.Enter) { }
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
Key key;
|
|
if (key == Key.Enter) { }
|
|
|
|
// Modifiers
|
|
if (key.Shift) { }
|
|
if (key.Ctrl) { }
|
|
|
|
// With modifiers
|
|
Key ctrlC = Key.C.WithCtrl;
|
|
Key shiftF1 = Key.F1.WithShift;
|
|
```
|
|
|
|
### Key Bindings
|
|
|
|
**v1:**
|
|
```csharp
|
|
// Override OnKeyPress
|
|
protected override bool OnKeyPress(KeyEvent keyEvent)
|
|
{
|
|
if (keyEvent.KeyCode == KeyCode.Enter)
|
|
{
|
|
// Handle
|
|
return true;
|
|
}
|
|
return base.OnKeyPress(keyEvent);
|
|
}
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Use KeyBindings + Commands
|
|
AddCommand(Command.Accept, HandleAccept);
|
|
KeyBindings.Add(Key.Enter, Command.Accept);
|
|
|
|
private bool HandleAccept()
|
|
{
|
|
// Handle
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Application-Wide Keys
|
|
|
|
**v1:**
|
|
```csharp
|
|
// Hard-coded Ctrl+Q
|
|
if (keyEvent.Key == Key.CtrlMask | Key.Q)
|
|
{
|
|
Application.RequestStop();
|
|
}
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Configurable quit key
|
|
if (key == Application.QuitKey)
|
|
{
|
|
Application.RequestStop();
|
|
}
|
|
|
|
// Change the quit key
|
|
Application.QuitKey = Key.Esc;
|
|
```
|
|
|
|
### Navigation Keys
|
|
|
|
v2 has consistent, configurable navigation keys:
|
|
|
|
| Key | Purpose |
|
|
|-----|---------|
|
|
| `Tab` | Next TabStop |
|
|
| `Shift+Tab` | Previous TabStop |
|
|
| `F6` | Next TabGroup |
|
|
| `Shift+F6` | Previous TabGroup |
|
|
|
|
```csharp
|
|
// Configurable
|
|
Application.NextTabStopKey = Key.Tab;
|
|
Application.PrevTabStopKey = Key.Tab.WithShift;
|
|
Application.NextTabGroupKey = Key.F6;
|
|
Application.PrevTabGroupKey = Key.F6.WithShift;
|
|
```
|
|
|
|
See [Keyboard Deep Dive](keyboard.md) for complete details.
|
|
|
|
---
|
|
|
|
## Mouse API
|
|
|
|
### MouseEventEventArgs → MouseEventArgs
|
|
|
|
**v1:**
|
|
```csharp
|
|
void HandleMouse(MouseEventEventArgs args) { }
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
void HandleMouse(object? sender, MouseEventArgs args) { }
|
|
```
|
|
|
|
### Mouse Coordinates
|
|
|
|
**v1:**
|
|
- Mouse coordinates were screen-relative
|
|
|
|
**v2:**
|
|
- Mouse coordinates are now **Viewport-relative**
|
|
|
|
```csharp
|
|
// v2 - Viewport-relative coordinates
|
|
view.MouseEvent += (s, e) =>
|
|
{
|
|
// e.Position is relative to view's Viewport
|
|
var x = e.Position.X; // 0 = left edge of viewport
|
|
var y = e.Position.Y; // 0 = top edge of viewport
|
|
};
|
|
```
|
|
|
|
### Mouse Click Handling
|
|
|
|
**v1:**
|
|
```csharp
|
|
// v1 - MouseClick event
|
|
view.MouseClick += (mouseEvent) =>
|
|
{
|
|
// Handle click
|
|
DoSomething();
|
|
};
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// v2 - Use MouseBindings + Commands + Selecting event
|
|
view.MouseBindings.Add(MouseFlags.Button1Clicked, Command.Select);
|
|
view.Selecting += (s, e) =>
|
|
{
|
|
// Handle selection (called when Button1Clicked)
|
|
DoSomething();
|
|
};
|
|
|
|
// Alternative: Use MouseEvent for low-level handling
|
|
view.MouseEvent += (s, e) =>
|
|
{
|
|
if (e.Flags.HasFlag(MouseFlags.Button1Clicked))
|
|
{
|
|
DoSomething();
|
|
e.Handled = true;
|
|
}
|
|
};
|
|
```
|
|
|
|
**Key Changes:**
|
|
- `View.MouseClick` event has been **removed**
|
|
- Use `MouseBindings` to map mouse events to `Command`s
|
|
- Default mouse bindings invoke `Command.Select` which raises the `Selecting` event
|
|
- For custom behavior, override `OnSelecting` or subscribe to the `Selecting` event
|
|
- For low-level mouse handling, use `MouseEvent` directly
|
|
|
|
**Migration Pattern:**
|
|
```csharp
|
|
// ❌ v1 - OnMouseClick override
|
|
protected override bool OnMouseClick(MouseEventArgs mouseEvent)
|
|
{
|
|
if (mouseEvent.Flags.HasFlag(MouseFlags.Button1Clicked))
|
|
{
|
|
PerformAction();
|
|
return true;
|
|
}
|
|
return base.OnMouseClick(mouseEvent);
|
|
}
|
|
|
|
// ✅ v2 - OnSelecting override
|
|
protected override bool OnSelecting(CommandEventArgs args)
|
|
{
|
|
if (args.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
|
|
{
|
|
// Access mouse position and flags via context
|
|
if (mouseArgs.Flags.HasFlag(MouseFlags.Button1Clicked))
|
|
{
|
|
PerformAction();
|
|
return true;
|
|
}
|
|
}
|
|
return base.OnSelecting(args);
|
|
}
|
|
|
|
// ✅ v2 - Selecting event (simpler)
|
|
view.Selecting += (s, e) =>
|
|
{
|
|
PerformAction();
|
|
e.Handled = true;
|
|
};
|
|
```
|
|
|
|
**Accessing Mouse Position in Selecting Event:**
|
|
```csharp
|
|
view.Selecting += (s, e) =>
|
|
{
|
|
// Extract mouse event args from command context
|
|
if (e.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
|
|
{
|
|
Point position = mouseArgs.Position;
|
|
MouseFlags flags = mouseArgs.Flags;
|
|
|
|
// Use position and flags for custom logic
|
|
HandleClick(position, flags);
|
|
e.Handled = true;
|
|
}
|
|
};
|
|
```
|
|
|
|
### Mouse State and Highlighting
|
|
|
|
v2 adds enhanced mouse state tracking:
|
|
|
|
```csharp
|
|
// Configure which mouse states trigger highlighting
|
|
view.HighlightStates = MouseState.In | MouseState.Pressed;
|
|
|
|
// React to mouse state changes
|
|
view.MouseStateChanged += (s, e) =>
|
|
{
|
|
switch (e.Value)
|
|
{
|
|
case MouseState.In:
|
|
// Mouse entered view
|
|
break;
|
|
case MouseState.Pressed:
|
|
// Mouse button pressed in view
|
|
break;
|
|
}
|
|
};
|
|
```
|
|
|
|
See [Mouse Deep Dive](mouse.md) for complete details.
|
|
|
|
---
|
|
|
|
## Navigation Changes
|
|
|
|
### Focus Properties
|
|
|
|
**v1:**
|
|
```csharp
|
|
view.CanFocus = true; // Default was true
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
view.CanFocus = true; // Default is FALSE - must opt-in
|
|
```
|
|
|
|
**Important:** In v2, `CanFocus` defaults to `false`. Views that want focus must explicitly set it.
|
|
|
|
### Focus Changes
|
|
|
|
**v1:**
|
|
```csharp
|
|
// HasFocus was read-only
|
|
bool hasFocus = view.HasFocus;
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// HasFocus can be set
|
|
view.HasFocus = true; // Equivalent to SetFocus()
|
|
view.HasFocus = false; // Equivalent to SuperView.AdvanceFocus()
|
|
```
|
|
|
|
### TabStop Behavior
|
|
|
|
**v1:**
|
|
```csharp
|
|
view.TabStop = true; // Boolean
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
view.TabStop = TabBehavior.TabStop; // Enum with more options
|
|
|
|
// Options:
|
|
// - NoStop: Focusable but not via Tab
|
|
// - TabStop: Normal tab navigation
|
|
// - TabGroup: Advance via F6
|
|
```
|
|
|
|
### Navigation Events
|
|
|
|
**v1:**
|
|
```csharp
|
|
view.Enter += (s, e) => { }; // Gained focus
|
|
view.Leave += (s, e) => { }; // Lost focus
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
view.HasFocusChanging += (s, e) =>
|
|
{
|
|
// Before focus changes (cancellable)
|
|
if (preventFocusChange)
|
|
e.Cancel = true;
|
|
};
|
|
|
|
view.HasFocusChanged += (s, e) =>
|
|
{
|
|
// After focus changed
|
|
if (e.Value)
|
|
Console.WriteLine("Gained focus");
|
|
else
|
|
Console.WriteLine("Lost focus");
|
|
};
|
|
```
|
|
|
|
See [Navigation Deep Dive](navigation.md) for complete details.
|
|
|
|
---
|
|
|
|
## Scrolling Changes
|
|
|
|
### ScrollView Removed
|
|
|
|
**v1:**
|
|
```csharp
|
|
var scrollView = new ScrollView
|
|
{
|
|
ContentSize = new Size(100, 100),
|
|
ShowHorizontalScrollIndicator = true,
|
|
ShowVerticalScrollIndicator = true
|
|
};
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Built-in scrolling on every View
|
|
var view = new View();
|
|
view.SetContentSize(new Size(100, 100));
|
|
|
|
// Built-in scrollbars
|
|
view.VerticalScrollBar.Visible = true;
|
|
view.HorizontalScrollBar.Visible = true;
|
|
view.VerticalScrollBar.AutoShow = true;
|
|
```
|
|
|
|
### Scrolling API
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Set content larger than viewport
|
|
view.SetContentSize(new Size(100, 100));
|
|
|
|
// Scroll by changing Viewport location
|
|
view.Viewport = view.Viewport with { Location = new Point(10, 10) };
|
|
|
|
// Or use helper methods
|
|
view.ScrollVertical(5);
|
|
view.ScrollHorizontal(3);
|
|
```
|
|
|
|
See [Scrolling Deep Dive](scrolling.md) for complete details.
|
|
|
|
---
|
|
|
|
## Event Pattern Changes
|
|
|
|
v2 standardizes all events to use `object sender, EventArgs args` pattern.
|
|
|
|
### Button.Clicked → Button.Accepting
|
|
|
|
**v1:**
|
|
```csharp
|
|
button.Clicked += () => { /* do something */ };
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
button.Accepting += (s, e) => { /* do something */ };
|
|
```
|
|
|
|
### Event Signatures
|
|
|
|
**v1:**
|
|
```csharp
|
|
// Various patterns
|
|
event Action SomeEvent;
|
|
event Action<string> OtherEvent;
|
|
event Action<EventArgs> ThirdEvent;
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Consistent pattern
|
|
event EventHandler<EventArgs>? SomeEvent;
|
|
event EventHandler<EventArgs<string>>? OtherEvent;
|
|
event EventHandler<CancelEventArgs<bool>>? ThirdEvent;
|
|
```
|
|
|
|
**Benefits:**
|
|
- Named parameters
|
|
- Cancellable events via `CancelEventArgs`
|
|
- Future-proof (new properties can be added)
|
|
|
|
---
|
|
|
|
## View-Specific Changes
|
|
|
|
### CheckBox
|
|
|
|
**v1:**
|
|
```csharp
|
|
var cb = new CheckBox("_Checkbox", true);
|
|
cb.Toggled += (e) => { };
|
|
cb.Toggle();
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
var cb = new CheckBox
|
|
{
|
|
Title = "_Checkbox",
|
|
CheckState = CheckState.Checked
|
|
};
|
|
cb.CheckStateChanging += (s, e) =>
|
|
{
|
|
e.Cancel = preventChange;
|
|
};
|
|
cb.AdvanceCheckState();
|
|
```
|
|
|
|
### StatusBar
|
|
|
|
**v1:**
|
|
```csharp
|
|
var statusBar = new StatusBar(
|
|
new StatusItem[]
|
|
{
|
|
new StatusItem(Application.QuitKey, "Quit", () => Quit())
|
|
}
|
|
);
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
var statusBar = new StatusBar(
|
|
new Shortcut[]
|
|
{
|
|
new Shortcut(Application.QuitKey, "Quit", Quit)
|
|
}
|
|
);
|
|
```
|
|
|
|
### PopoverMenu
|
|
|
|
v2 replaces `ContextMenu` with `PopoverMenu`:
|
|
|
|
**v1:**
|
|
```csharp
|
|
var contextMenu = new ContextMenu();
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
var popoverMenu = new PopoverMenu();
|
|
```
|
|
|
|
### MenuItem
|
|
|
|
**v1:**
|
|
```csharp
|
|
new MenuItem(
|
|
"Copy",
|
|
"",
|
|
CopyGlyph,
|
|
null,
|
|
null,
|
|
(KeyCode)Key.G.WithCtrl
|
|
)
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
new MenuItem(
|
|
"Copy",
|
|
"",
|
|
CopyGlyph,
|
|
Key.G.WithCtrl
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Disposal and Resource Management
|
|
|
|
v2 implements proper `IDisposable` throughout.
|
|
|
|
### View Disposal
|
|
|
|
```csharp
|
|
// v1 - No explicit disposal needed
|
|
var view = new View();
|
|
Application.Run(view);
|
|
Application.Shutdown();
|
|
|
|
// v2 - Explicit disposal required
|
|
var view = new View();
|
|
app.Run(view);
|
|
view.Dispose();
|
|
app.Dispose();
|
|
```
|
|
|
|
### Disposal Patterns
|
|
|
|
```csharp
|
|
// ✅ Best practice - using statement
|
|
using (var app = Application.Create().Init())
|
|
{
|
|
using (var view = new View())
|
|
{
|
|
app.Run(view);
|
|
}
|
|
}
|
|
|
|
// ✅ Alternative - explicit try/finally
|
|
var app = Application.Create();
|
|
try
|
|
{
|
|
app.Init();
|
|
var view = new View();
|
|
try
|
|
{
|
|
app.Run(view);
|
|
}
|
|
finally
|
|
{
|
|
view.Dispose();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
app.Dispose();
|
|
}
|
|
```
|
|
|
|
### SubView Disposal
|
|
|
|
When a View is disposed, it automatically disposes all SubViews:
|
|
|
|
```csharp
|
|
var container = new View();
|
|
var child1 = new View();
|
|
var child2 = new View();
|
|
|
|
container.Add(child1, child2);
|
|
|
|
// Disposes container, child1, and child2
|
|
container.Dispose();
|
|
```
|
|
|
|
See [Resource Management](#disposal-and-resource-management) for complete details.
|
|
|
|
---
|
|
|
|
## API Terminology Changes
|
|
|
|
v2 modernizes terminology for clarity:
|
|
|
|
### Application.Top → Application.TopRunnable
|
|
|
|
**v1:**
|
|
```csharp
|
|
Application.Top.SetNeedsDraw();
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Use TopRunnable (or TopRunnableView for View reference)
|
|
app.TopRunnable?.SetNeedsDraw();
|
|
app.TopRunnableView?.SetNeedsDraw();
|
|
|
|
// From within a view
|
|
App?.TopRunnableView?.SetNeedsDraw();
|
|
```
|
|
|
|
**Why "TopRunnable"?**
|
|
- Clearly indicates it's the top of the runnable session stack
|
|
- Aligns with `IRunnable` architecture
|
|
- Works with any `IRunnable`, not just `Toplevel`
|
|
|
|
### Application.TopLevels → Application.SessionStack
|
|
|
|
**v1:**
|
|
```csharp
|
|
foreach (var tl in Application.TopLevels)
|
|
{
|
|
// Process
|
|
}
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
foreach (var token in app.SessionStack)
|
|
{
|
|
var runnable = token.Runnable;
|
|
// Process
|
|
}
|
|
|
|
// Count of sessions
|
|
int sessionCount = app.SessionStack.Count;
|
|
```
|
|
|
|
**Why "SessionStack"?**
|
|
- Describes both content (sessions) and structure (stack)
|
|
- Aligns with `SessionToken` terminology
|
|
- Follows .NET naming patterns
|
|
|
|
### View Arrangement
|
|
|
|
**v1:**
|
|
```csharp
|
|
view.SendSubViewToBack();
|
|
view.SendSubViewBackward();
|
|
view.SendSubViewToFront();
|
|
view.SendSubViewForward();
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
// Fixed naming (methods worked opposite to their names in v1)
|
|
view.MoveSubViewToStart();
|
|
view.MoveSubViewTowardsStart();
|
|
view.MoveSubViewToEnd();
|
|
view.MoveSubViewTowardsEnd();
|
|
```
|
|
|
|
### Mdi → ViewArrangement.Overlapped
|
|
|
|
**v1:**
|
|
```csharp
|
|
Application.MdiTop = true;
|
|
toplevel.IsMdiContainer = true;
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
view.Arrangement = ViewArrangement.Overlapped;
|
|
|
|
// Additional flags
|
|
view.Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable;
|
|
```
|
|
|
|
See [Arrangement Deep Dive](arrangement.md) for complete details.
|
|
|
|
---
|
|
|
|
## Complete Migration Example
|
|
|
|
Here's a complete v1 to v2 migration:
|
|
|
|
**v1:**
|
|
```csharp
|
|
using NStack;
|
|
using Terminal.Gui;
|
|
|
|
Application.Init();
|
|
|
|
var win = new Window(new Rect(0, 0, 50, 20), "Hello");
|
|
|
|
var label = new Label(1, 1, "Name:");
|
|
|
|
var textField = new TextField(10, 1, 30, "");
|
|
|
|
var button = new Button(10, 3, "OK");
|
|
button.Clicked += () =>
|
|
{
|
|
MessageBox.Query(50, 7, "Info", $"Hello, {textField.Text}", "Ok");
|
|
};
|
|
|
|
win.Add(label, textField, button);
|
|
|
|
Application.Top.Add(win);
|
|
Application.Run();
|
|
Application.Shutdown();
|
|
```
|
|
|
|
**v2:**
|
|
```csharp
|
|
using System;
|
|
using Terminal.Gui;
|
|
|
|
using (var app = Application.Create().Init())
|
|
{
|
|
var win = new Window
|
|
{
|
|
Title = "Hello",
|
|
Width = 50,
|
|
Height = 20
|
|
};
|
|
|
|
var label = new Label
|
|
{
|
|
Text = "Name:",
|
|
X = 1,
|
|
Y = 1
|
|
};
|
|
|
|
var textField = new TextField
|
|
{
|
|
X = 10,
|
|
Y = 1,
|
|
Width = 30
|
|
};
|
|
|
|
var button = new Button
|
|
{
|
|
Text = "OK",
|
|
X = 10,
|
|
Y = 3
|
|
};
|
|
button.Accepting += (s, e) =>
|
|
{
|
|
MessageBox.Query(app, "Info", $"Hello, {textField.Text}", "Ok");
|
|
};
|
|
|
|
win.Add(label, textField, button);
|
|
|
|
app.Run(win);
|
|
win.Dispose();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Summary of Major Breaking Changes
|
|
|
|
| Category | v1 | v2 |
|
|
|----------|----|----|
|
|
| **Application** | Static `Application` | `IApplication` instances via `Application.Create()` |
|
|
| **Disposal** | Automatic | Explicit (`IDisposable` pattern) |
|
|
| **View Construction** | Constructors with Rect | Initializers with X, Y, Width, Height |
|
|
| **Layout** | Absolute/Computed distinction | Unified Pos/Dim system |
|
|
| **Colors** | Limited palette | 24-bit TrueColor default |
|
|
| **Types** | `Rect`, `NStack.ustring` | `Rectangle`, `System.String` |
|
|
| **Keyboard** | `KeyEvent`, hard-coded keys | `Key`, configurable bindings |
|
|
| **Mouse** | Screen-relative | Viewport-relative |
|
|
| **Scrolling** | `ScrollView` | Built-in on all Views |
|
|
| **Focus** | `CanFocus` default true | `CanFocus` default false |
|
|
| **Navigation** | `Enter`/`Leave` events | `HasFocusChanging`/`HasFocusChanged` |
|
|
| **Events** | Mixed patterns | Standard `EventHandler<EventArgs>` |
|
|
| **Terminology** | `Application.Top`, `TopLevels` | `TopRunnable`, `SessionStack` |
|
|
|
|
---
|
|
|
|
## Additional Resources
|
|
|
|
- [Application Deep Dive](application.md) - Complete application architecture
|
|
- [View Deep Dive](View.md) - View system details
|
|
- [Layout Deep Dive](layout.md) - Comprehensive layout guide
|
|
- [Keyboard Deep Dive](keyboard.md) - Keyboard input handling
|
|
- [Mouse Deep Dive](mouse.md) - Mouse input handling
|
|
- [Navigation Deep Dive](navigation.md) - Focus and navigation
|
|
- [Scrolling Deep Dive](scrolling.md) - Built-in scrolling system
|
|
- [Arrangement Deep Dive](arrangement.md) - Movable/resizable views
|
|
- [Configuration Deep Dive](config.md) - Configuration system
|
|
- [What's New in v2](newinv2.md) - New features overview
|
|
|
|
---
|
|
|
|
## Getting Help
|
|
|
|
- [GitHub Discussions](https://github.com/gui-cs/Terminal.Gui/discussions)
|
|
- [GitHub Issues](https://github.com/gui-cs/Terminal.Gui/issues)
|
|
- [API Documentation](~/api/index.md) |