# 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` for type-safe, runnable views: ```csharp // Create a dialog that returns a typed result public class FileDialog : Runnable { 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(); string? result = app.GetResult(); 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(); // Dialog disposed automatically var result = app.GetResult(); } ``` **Disposal Rules:** - "Whoever creates it, owns it" - `Run()`: 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.MouseClick += (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 }; ``` ### Highlight Event v2 adds a `Highlight` event for visual feedback: ```csharp view.Highlight += (s, e) => { // Provide visual feedback on mouse hover }; view.HighlightStyle = HighlightStyle.Hover; ``` 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 OtherEvent; event Action ThirdEvent; ``` **v2:** ```csharp // Consistent pattern event EventHandler? SomeEvent; event EventHandler>? OtherEvent; event EventHandler>? 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` | | **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)