diff --git a/Example/demo.cs b/Example/demo.cs
index 53974b3c0..95a5726e6 100644
--- a/Example/demo.cs
+++ b/Example/demo.cs
@@ -190,7 +190,7 @@ static class Demo {
new DateField (3, 22, DateTime.Now),
new DateField (23, 22, DateTime.Now, true),
progress,
- new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"),
+ new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) or Ctrl+T to activate the menubar"),
menuKeysStyle,
menuAutoMouseNav
@@ -636,10 +636,23 @@ static class Demo {
};
#endif
+ win.KeyPress += Win_KeyPress;
+
top.Add (win);
//top.Add (menu);
top.Add (menu, statusBar);
Application.Run ();
}
+
+ private static void Win_KeyPress (object sender, View.KeyEventEventArgs e)
+ {
+ if (e.KeyEvent.Key == Key.ControlT) {
+ if (menu.IsMenuOpen)
+ menu.CloseMenu ();
+ else
+ menu.OpenMenu ();
+ e.Handled = true;
+ }
+ }
}
diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs
index c6568fb3e..a81647195 100644
--- a/Terminal.Gui/Core.cs
+++ b/Terminal.Gui/Core.cs
@@ -1087,6 +1087,11 @@ namespace Terminal.Gui {
/// The for the event.
///
public KeyEvent KeyEvent { get; set; }
+ ///
+ /// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber.
+ /// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
+ ///
+ public bool Handled { get; set; } = false;
}
///
@@ -1097,7 +1102,11 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent keyEvent)
{
- KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent));
+
+ KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
+ KeyPress?.Invoke (this, args);
+ if (args.Handled)
+ return true;
if (Focused?.ProcessKey (keyEvent) == true)
return true;
@@ -1107,7 +1116,10 @@ namespace Terminal.Gui {
///
public override bool ProcessHotKey (KeyEvent keyEvent)
{
- KeyPress?.Invoke (this, new KeyEventEventArgs (keyEvent));
+ KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
+ KeyPress?.Invoke (this, args);
+ if (args.Handled)
+ return true;
if (subviews == null || subviews.Count == 0)
return false;
foreach (var view in subviews)
@@ -1119,7 +1131,10 @@ namespace Terminal.Gui {
///
public override bool ProcessColdKey (KeyEvent keyEvent)
{
- KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent));
+ KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
+ KeyPress?.Invoke (this, args);
+ if (args.Handled)
+ return true;
if (subviews == null || subviews.Count == 0)
return false;
foreach (var view in subviews)
@@ -1136,7 +1151,10 @@ namespace Terminal.Gui {
/// Contains the details about the key that produced the event.
public override bool OnKeyDown (KeyEvent keyEvent)
{
- KeyDown?.Invoke (this, new KeyEventEventArgs (keyEvent));
+ KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
+ KeyDown?.Invoke (this, args);
+ if (args.Handled)
+ return true;
if (subviews == null || subviews.Count == 0)
return false;
foreach (var view in subviews)
@@ -1154,7 +1172,10 @@ namespace Terminal.Gui {
/// Contains the details about the key that produced the event.
public override bool OnKeyUp (KeyEvent keyEvent)
{
- KeyUp?.Invoke (this, new KeyEventEventArgs (keyEvent));
+ KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
+ KeyUp?.Invoke (this, args);
+ if (args.Handled)
+ return true;
if (subviews == null || subviews.Count == 0)
return false;
foreach (var view in subviews)
diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs
index da16a08ef..5f25cb797 100644
--- a/Terminal.Gui/Views/Menu.cs
+++ b/Terminal.Gui/Views/Menu.cs
@@ -784,7 +784,7 @@ namespace Terminal.Gui {
///
/// Closes the current Menu programatically, if open.
///
- public void CloseMenu()
+ public void CloseMenu ()
{
CloseMenu (false, false);
}
diff --git a/UICatalog/Program.cs b/UICatalog/Program.cs
deleted file mode 100644
index 86ad5405d..000000000
--- a/UICatalog/Program.cs
+++ /dev/null
@@ -1,280 +0,0 @@
-using NStack;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.Linq;
-using Terminal.Gui;
-
-namespace UICatalog {
- ///
- /// Main program for the Terminal.gui UI Catalog app. This app provides a chooser that allows
- /// for a calalog of UI demos, examples, and tests.
- ///
- internal class Program {
- private static Toplevel _top;
- private static MenuBar _menu;
- private static int _nameColumnWidth;
- private static Window _leftPane;
- private static List _categories;
- private static ListView _categoryListView;
- private static Window _rightPane;
- private static List _scenarios;
- private static ListView _scenarioListView;
- private static StatusBar _statusBar;
-
- private static Scenario _selectedScenario = null;
-
- static void Main (string [] args)
- {
- if (Debugger.IsAttached)
- CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
-
- _scenarios = Scenario.GetDerivedClassesCollection ().OrderBy (t => Scenario.ScenarioMetadata.GetName (t)).ToList();
-
- if (args.Length > 0) {
- var item = _scenarios.FindIndex (t => Scenario.ScenarioMetadata.GetName (t).Equals (args [0], StringComparison.OrdinalIgnoreCase));
- _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item]);
- _selectedScenario.Init (Application.Top);
- _selectedScenario.Setup ();
- _selectedScenario.Run ();
- _selectedScenario = null;
- return;
- }
-
- Scenario scenario = GetScenarioToRun ();
- while (scenario != null) {
- scenario.Init (Application.Top);
- scenario.Setup ();
- scenario.Run ();
- scenario = GetScenarioToRun ();
- }
-
- }
-
- ///
- /// Create all controls. This gets called once and the controls remain with their state between Sceanrio runs.
- ///
- private static void Setup ()
- {
- _menu = new MenuBar (new MenuBarItem [] {
- new MenuBarItem ("_File", new MenuItem [] {
- new MenuItem ("_Quit", "", () => Application.RequestStop() )
- }),
- new MenuBarItem ("_About...", "About this app", () => MessageBox.Query (0, 10, "About UI Catalog", "UI Catalog is a comprehensive sample library for Terminal.Gui", "Ok")),
- });
-
- _leftPane = new Window ("Categories") {
- X = 0,
- Y = 1, // for menu
- Width = 25,
- Height = Dim.Fill (),
- CanFocus = false,
- };
-
-
- _categories = Scenario.GetAllCategories ().OrderBy(c => c).ToList();
- _categoryListView = new ListView (_categories) {
- X = 1,
- Y = 0,
- Width = Dim.Fill (0),
- Height = Dim.Fill (2),
- AllowsMarking = false,
- CanFocus = true,
- };
- _categoryListView.OpenSelectedItem += (o, a) => {
- _top.SetFocus (_rightPane);
- };
- _categoryListView.SelectedChanged += CategoryListView_SelectedChanged;
- _leftPane.Add (_categoryListView);
-
- _rightPane = new Window ("Scenarios") {
- X = 25,
- Y = 1, // for menu
- Width = Dim.Fill (),
- Height = Dim.Fill (),
- CanFocus = false,
-
- };
-
- _nameColumnWidth = Scenario.ScenarioMetadata.GetName (_scenarios.OrderByDescending (t => Scenario.ScenarioMetadata.GetName (t).Length).FirstOrDefault ()).Length;
-
- _scenarioListView = new ListView () {
- X = 0,
- Y = 0,
- Width = Dim.Fill (0),
- Height = Dim.Fill (0),
- AllowsMarking = false,
- CanFocus = true,
- };
-
- //_scenarioListView.OnKeyPress += (KeyEvent ke) => {
- // if (_top.MostFocused == _scenarioListView && ke.Key == Key.Enter) {
- // _scenarioListView_OpenSelectedItem (null, null);
- // }
- //};
-
- _scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
- _rightPane.Add (_scenarioListView);
-
- _categoryListView.SelectedItem = 0;
- _categoryListView.OnSelectedChanged ();
-
- _statusBar = new StatusBar (new StatusItem [] {
- //new StatusItem(Key.F1, "~F1~ Help", () => Help()),
- new StatusItem(Key.ControlQ, "~CTRL-Q~ Quit", () => {
- if (_selectedScenario is null){
- // This causes GetScenarioToRun to return null
- _selectedScenario = null;
- Application.RequestStop();
- } else {
- _selectedScenario.RequestStop();
- }
- }),
- });
- }
-
- ///
- /// This shows the selection UI. Each time it is run, it calls Application.Init to reset everything.
- ///
- ///
- private static Scenario GetScenarioToRun ()
- {
- Application.Init ();
-
- if (_menu == null) {
- Setup ();
- }
-
- _top = Application.Top;
-
- _top.KeyUp += KeyUpHandler;
-
- _top.Add (_menu);
- _top.Add (_leftPane);
- _top.Add (_rightPane);
- _top.Add (_statusBar);
-
- // HACK: There is no other way to SetFocus before Application.Run. See Issue #445
-#if false
- if (_runningScenario != null)
- Application.Iteration += Application_Iteration;
-#else
- _top.Ready += (o, a) => {
- if (_selectedScenario != null) {
- _top.SetFocus (_rightPane);
- _selectedScenario = null;
- }
- };
-#endif
-
- Application.Run (_top);
- Application.Shutdown ();
- return _selectedScenario;
- }
-
-#if false
- private static void Application_Iteration (object sender, EventArgs e)
- {
- Application.Iteration -= Application_Iteration;
- _top.SetFocus (_rightPane);
- }
-#endif
- private static void _scenarioListView_OpenSelectedItem (object sender, EventArgs e)
- {
- if (_selectedScenario is null) {
- var source = _scenarioListView.Source as ScenarioListDataSource;
- _selectedScenario = (Scenario)Activator.CreateInstance (source.Scenarios [_scenarioListView.SelectedItem]);
- Application.RequestStop ();
- }
- }
-
- internal class ScenarioListDataSource : IListDataSource {
- public List Scenarios { get; set; }
-
- public bool IsMarked (int item) => false;// Scenarios [item].IsMarked;
-
- public int Count => Scenarios.Count;
-
- public ScenarioListDataSource (List itemList) => Scenarios = itemList;
-
- public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width)
- {
- container.Move (col, line);
- // Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
- var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
- RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width);
- }
-
- public void SetMark (int item, bool value)
- {
- }
-
- // A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
- private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
- {
- int used = 0;
- int index = 0;
- while (index < ustr.Length) {
- (var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
- var count = Rune.ColumnWidth (rune);
- if (used + count >= width) break;
- driver.AddRune (rune);
- used += count;
- index += size;
- }
-
- while (used < width) {
- driver.AddRune (' ');
- used++;
- }
- }
-
- public IList ToList ()
- {
- return Scenarios;
- }
-
- }
-
- ///
- /// When Scenarios are running we need to override the behavior of the Menu
- /// and Statusbar to enable Scenarios that use those (or related key input)
- /// to not be impacted. Same as for tabs.
- ///
- ///
- private static void KeyUpHandler (object sender, View.KeyEventEventArgs a)
- {
- if (_selectedScenario != null) {
- //switch (ke.Key) {
- //case Key.Esc:
- // //_runningScenario.RequestStop ();
- // break;
- //case Key.Enter:
- // break;
- //}<
- } else if (a.KeyEvent.Key == Key.Tab || a.KeyEvent.Key == Key.BackTab) {
- // BUGBUG: Work around Issue #434 by implementing our own TAB navigation
- if (_top.MostFocused == _categoryListView)
- _top.SetFocus (_rightPane);
- else
- _top.SetFocus (_leftPane);
- }
- }
-
- private static void CategoryListView_SelectedChanged (object sender, ListViewItemEventArgs e)
- {
- var item = _categories [_categoryListView.SelectedItem];
- List newlist;
- if (item.Equals ("All")) {
- newlist = _scenarios;
-
- } else {
- newlist = _scenarios.Where (t => Scenario.ScenarioCategory.GetCategories (t).Contains (item)).ToList ();
- }
- _scenarioListView.Source = new ScenarioListDataSource (newlist);
- _scenarioListView.SelectedItem = 0;
- }
- }
-}
diff --git a/UICatalog/Scenarios/Threading.cs b/UICatalog/Scenarios/Threading.cs
new file mode 100644
index 000000000..28b2a72d7
--- /dev/null
+++ b/UICatalog/Scenarios/Threading.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Terminal.Gui;
+
+namespace UICatalog {
+ [ScenarioMetadata (Name: "Threading", Description: "Demonstration of how to use threading in different ways")]
+ [ScenarioCategory ("Threading")]
+ class Threading : Scenario {
+ private Action _action;
+ private Action _lambda;
+ private EventHandler _handler;
+ private Action _sync;
+
+ private ListView _itemsList;
+ private Button _btnActionCancel;
+ List log = new List ();
+ private ListView _logJob;
+
+ public override void Setup ()
+ {
+ _action = LoadData;
+ _lambda = async () => {
+ _itemsList.Source = null;
+ LogJob ("Loading task lambda");
+ var items = await LoadDataAsync ();
+ LogJob ("Returning from task lambda");
+ _itemsList.SetSource (items);
+ };
+ _handler = async (s, e) => {
+ _itemsList.Source = null;
+ LogJob ("Loading task handler");
+ var items = await LoadDataAsync ();
+ LogJob ("Returning from task handler");
+ _itemsList.SetSource (items);
+
+ };
+ _sync = () => {
+ _itemsList.Source = null;
+ LogJob ("Loading task synchronous");
+ List items = new List () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
+ LogJob ("Returning from task synchronous");
+ _itemsList.SetSource (items);
+ };
+
+ Application.Init ();
+
+ _btnActionCancel = new Button (1, 1, "Cancelable Load Items");
+ _btnActionCancel.Clicked += () => Application.MainLoop.Invoke (CallLoadItemsAsync);
+
+ _itemsList = new ListView {
+ X = Pos.X (_btnActionCancel),
+ Y = Pos.Y (_btnActionCancel) + 4,
+ Width = 10,
+ Height = 10
+ };
+
+ _logJob = new ListView (log) {
+ X = Pos.Right (_itemsList) + 10,
+ Y = Pos.Y (_itemsList),
+ Width = 50,
+ Height = Dim.Fill ()
+ };
+
+ var text = new TextField (1, 3, 100, "Type anything after press the button");
+
+ var _btnAction = new Button (80, 10, "Load Data Action");
+ _btnAction.Clicked += () => _action.Invoke ();
+ var _btnLambda = new Button (80, 12, "Load Data Lambda");
+ _btnLambda.Clicked += () => _lambda.Invoke ();
+ var _btnHandler = new Button (80, 14, "Load Data Handler");
+ _btnHandler.Clicked += () => _handler.Invoke (null, new EventArgs ());
+ var _btnSync = new Button (80, 16, "Load Data Synchronous");
+ _btnSync.Clicked += () => _sync.Invoke ();
+ var _btnMethod = new Button (80, 18, "Load Data Method");
+ _btnMethod.Clicked += async () => await MethodAsync ();
+ var _btnClearData = new Button (80, 20, "Clear Data");
+ _btnClearData.Clicked += () => { _itemsList.Source = null; LogJob ("Cleaning Data"); };
+ var _btnQuit = new Button (80, 22, "Quit");
+ _btnQuit.Clicked += Application.RequestStop;
+
+ Win.Add (_itemsList, _btnActionCancel, _logJob, text, _btnAction, _btnLambda, _btnHandler, _btnSync, _btnMethod, _btnClearData, _btnQuit);
+ Application.Top.Add (Win);
+ Application.Run ();
+
+ }
+
+ private async void LoadData ()
+ {
+ _itemsList.Source = null;
+ LogJob ("Loading task");
+ var items = await LoadDataAsync ();
+ LogJob ("Returning from task");
+ _itemsList.SetSource (items);
+ }
+
+ private void LogJob (string job)
+ {
+ log.Add (job);
+ _logJob.MoveDown ();
+ }
+
+ private async Task> LoadDataAsync ()
+ {
+ _itemsList.Source = null;
+ LogJob ("Starting delay");
+ await Task.Delay (3000);
+ LogJob ("Finished delay");
+ return new List () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
+ }
+
+ private async Task MethodAsync ()
+ {
+ _itemsList.Source = null;
+ LogJob ("Loading task method");
+ List items = new List () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
+ await Task.Delay (3000);
+ LogJob ("Returning from task method");
+ await _itemsList.SetSourceAsync (items);
+ }
+
+ private CancellationTokenSource cancellationTokenSource;
+
+ private async void CallLoadItemsAsync ()
+ {
+ cancellationTokenSource = new CancellationTokenSource ();
+ _itemsList.Source = null;
+ LogJob ($"Clicked the button");
+ if (_btnActionCancel.Text == "Cancel") {
+ _btnActionCancel.Text = "Cancelable Load Items";
+ cancellationTokenSource.Cancel ();
+ } else
+ _btnActionCancel.Text = "Cancel";
+ try {
+ if (cancellationTokenSource.Token.IsCancellationRequested)
+ cancellationTokenSource.Token.ThrowIfCancellationRequested ();
+ LogJob ($"Calling task Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
+ var items = await Task.Run (LoadItemsAsync, cancellationTokenSource.Token);
+ if (!cancellationTokenSource.IsCancellationRequested) {
+ LogJob ($"Returned from task Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
+ _itemsList.SetSource (items);
+ LogJob ($"Finished populate list view Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
+ _btnActionCancel.Text = "Load Items";
+ } else {
+ LogJob ("Task was canceled!");
+ }
+ } catch (OperationCanceledException ex) {
+ LogJob (ex.Message);
+ }
+ }
+
+ private async Task> LoadItemsAsync ()
+ {
+ // Do something that takes lot of times.
+ LogJob ($"Starting delay Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
+ await Task.Delay (5000);
+ LogJob ($"Finished delay Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
+ return new List () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
+ }
+ }
+}