浏览代码

Merge branch 'master' into progress_invoke_fix

Charlie Kindel 5 年之前
父节点
当前提交
fd2feb15fa
共有 5 个文件被更改,包括 203 次插入287 次删除
  1. 14 1
      Example/demo.cs
  2. 26 5
      Terminal.Gui/Core.cs
  3. 1 1
      Terminal.Gui/Views/Menu.cs
  4. 0 280
      UICatalog/Program.cs
  5. 162 0
      UICatalog/Scenarios/Threading.cs

+ 14 - 1
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;
+		}
+	}
 }

+ 26 - 5
Terminal.Gui/Core.cs

@@ -1087,6 +1087,11 @@ namespace Terminal.Gui {
 			/// The <see cref="KeyEvent"/> for the event.
 			/// </summary>
 			public KeyEvent KeyEvent { get; set; }
+			/// <summary>
+			/// 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.
+			/// </summary>
+			public bool Handled { get; set; } = false;
 		}
 
 		/// <summary>
@@ -1097,7 +1102,11 @@ namespace Terminal.Gui {
 		/// <inheritdoc cref="ProcessKey"/>
 		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 {
 		/// <inheritdoc cref="ProcessHotKey"/>
 		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 {
 		/// <inheritdoc cref="ProcessColdKey"/>
 		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 {
 		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
 		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 {
 		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
 		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)

+ 1 - 1
Terminal.Gui/Views/Menu.cs

@@ -784,7 +784,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Closes the current Menu programatically, if open.
 		/// </summary>
-		public void CloseMenu()
+		public void CloseMenu ()
 		{
 			CloseMenu (false, false);
 		}

+ 0 - 280
UICatalog/Program.cs

@@ -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 {
-	/// <summary>
-	/// 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.
-	/// </summary>
-	internal class Program {
-		private static Toplevel _top;
-		private static MenuBar _menu;
-		private static int _nameColumnWidth;
-		private static Window _leftPane;
-		private static List<string> _categories;
-		private static ListView _categoryListView;
-		private static Window _rightPane;
-		private static List<Type> _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 ();
-			}
-
-		}
-
-		/// <summary>
-		/// Create all controls. This gets called once and the controls remain with their state between Sceanrio runs.
-		/// </summary>
-		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();
-					}
-				}),
-			});
-		}
-
-		/// <summary>
-		/// This shows the selection UI. Each time it is run, it calls Application.Init to reset everything.
-		/// </summary>
-		/// <returns></returns>
-		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<Type> Scenarios { get; set; }
-
-			public bool IsMarked (int item) => false;//  Scenarios [item].IsMarked;
-
-			public int Count => Scenarios.Count;
-
-			public ScenarioListDataSource (List<Type> 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;
-			}
-
-		}
-
-		/// <summary>
-		/// 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.
-		/// </summary>
-		/// <param name="ke"></param>
-		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<Type> 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;
-		}
-	}
-}

+ 162 - 0
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<string> log = new List<string> ();
+		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<string> items = new List<string> () { "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<List<string>> LoadDataAsync ()
+		{
+			_itemsList.Source = null;
+			LogJob ("Starting delay");
+			await Task.Delay (3000);
+			LogJob ("Finished delay");
+			return new List<string> () { "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<string> items = new List<string> () { "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<List<string>> 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<string> () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
+		}
+	}
+}