Browse Source

merge with master

Charlie Kindel 5 years ago
parent
commit
4047eb035e

+ 29 - 20
Example/demo.cs

@@ -237,12 +237,13 @@ static class Demo {
 	//
 	static void Editor ()
 	{
-		var tframe = Application.Top.Frame;
-		Application.Top.RemoveAll ();
+		Application.Init ();
+
 		var ntop = Application.Top;
+
 		var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_Close", "", () => { if (Quit ()) {Application.RequestStop (); } }),
+				new MenuItem ("_Close", "", () => { if (Quit ()) { running = MainApp; Application.RequestStop (); } }),
 			}),
 			new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuItem ("_Copy", "", null),
@@ -267,15 +268,13 @@ static class Demo {
 		};
 		ntop.Add (win);
 
-		var text = new TextView (new Rect (0, 0, tframe.Width - 2, tframe.Height - 3));
+		var text = new TextView () { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
 
 		if (fname != null)
 			text.Text = System.IO.File.ReadAllText (fname);
 		win.Add (text);
 
 		Application.Run (ntop, false);
-		Application.Top.RemoveAll ();
-		Main ();
 	}
 
 	static bool Quit ()
@@ -450,20 +449,22 @@ static class Demo {
 
 	static void ComboBoxDemo ()
 	{
-		IList<string> items = new List<string> ();
-		foreach (var dir in new [] { "/etc", @"\windows\System32" }) {
+		//TODO: Duplicated code in ListsAndCombos.cs Consider moving to shared assembly
+		var items = new List<ustring> ();
+		foreach (var dir in new [] { "/etc", @$"{Environment.GetEnvironmentVariable ("SystemRoot")}\System32" }) {
 			if (Directory.Exists (dir)) {
 				items = Directory.GetFiles (dir).Union (Directory.GetDirectories (dir))
 					.Select (Path.GetFileName)
 					.Where (x => char.IsLetterOrDigit (x [0]))
-					.OrderBy (x => x).ToList ();
+					.OrderBy (x => x).Select (x => ustring.Make (x)).ToList ();
 			}
 		}
-		var list = new ComboBox () { X = 0, Y = 0, Width = Dim.Fill(), Height = Dim.Fill() };
-		list.SetSource(items.ToList());
-		list.SelectedItemChanged += (object sender, ustring text) => { Application.RequestStop (); };
+		var list = new ComboBox () { Width = Dim.Fill(), Height = Dim.Fill() };
+		list.SetSource(items);
+		list.OpenSelectedItem += (ListViewItemEventArgs text) => { Application.RequestStop (); };
 
-		var d = new Dialog ("Select source file", 40, 12) { list };
+		var d = new Dialog () { Title = "Select source file", Width = Dim.Percent (50), Height = Dim.Percent (50) };
+		d.Add (list);
 		Application.Run (d);
 
 		MessageBox.Query (60, 10, "Selected file", list.Text.ToString() == "" ? "Nothing selected" : list.Text.ToString(), "Ok");
@@ -534,11 +535,20 @@ static class Demo {
 	}
 	#endregion
 
+	public static Action running = MainApp;
+	static void Main ()
+	{
+		while (running != null) {
+			running.Invoke ();
+		}
+		Application.Shutdown ();
+	}
+
 	public static Label ml;
 	public static MenuBar menu;
 	public static CheckBox menuKeysStyle;
 	public static CheckBox menuAutoMouseNav;
-	static void Main ()
+	static void MainApp ()
 	{
 		if (Debugger.IsAttached)
 			CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
@@ -578,14 +588,14 @@ static class Demo {
 
 		menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("Text _Editor Demo", "", () => { Editor (); }),
+				new MenuItem ("Text _Editor Demo", "", () => { running = Editor; Application.RequestStop (); }),
 				new MenuItem ("_New", "Creates new file", NewFile),
 				new MenuItem ("_Open", "", Open),
 				new MenuItem ("_Hex", "", () => ShowHex (top)),
 				new MenuItem ("_Close", "", () => Close ()),
 				new MenuItem ("_Disabled", "", () => { }, () => false),
 				null,
-				new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
+				new MenuItem ("_Quit", "", () => { if (Quit ()) { running = null; top.Running = false; } })
 			}),
 			new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuItem ("_Copy", "", Copy),
@@ -648,9 +658,8 @@ static class Demo {
 			new StatusItem(Key.F1, "~F1~ Help", () => Help()),
 			new StatusItem(Key.F2, "~F2~ Load", Load),
 			new StatusItem(Key.F3, "~F3~ Save", Save),
-			new StatusItem(Key.ControlQ, "~^Q~ Quit", () => { if (Quit ()) top.Running = false; }),
-		}) {
-		};
+			new StatusItem(Key.ControlQ, "~^Q~ Quit", () => { if (Quit ()) { running = null; top.Running = false; } })
+		});
 
 		win.Add (drag, dragText);
 
@@ -671,7 +680,7 @@ static class Demo {
 		top.Add (win);
 		//top.Add (menu);
 		top.Add (menu, statusBar);
-		Application.Run ();
+		Application.Run (top, false);
 	}
 
 	private static void Win_KeyPress (View.KeyEventEventArgs e)

+ 11 - 4
Terminal.Gui/Core/Application.cs

@@ -589,16 +589,23 @@ namespace Terminal.Gui {
 
 					MainLoop.MainIteration ();
 					Iteration?.Invoke ();
-				} else if (wait == false)
+				} else if (wait == false) {
 					return;
-				if (state.Toplevel.NeedDisplay != null && (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay)) {
+				}
+				if (state.Toplevel != Top && (!Top.NeedDisplay.IsEmpty || Top.childNeedsDisplay)) {
+					Top.Redraw (Top.Bounds);
+					state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds);
+				}
+				if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
 					state.Toplevel.Redraw (state.Toplevel.Bounds);
-					if (DebugDrawBounds)
+					if (DebugDrawBounds) {
 						DrawBounds (state.Toplevel);
+					}
 					state.Toplevel.PositionCursor ();
 					Driver.Refresh ();
-				} else
+				} else {
 					Driver.UpdateCursor ();
+				}
 			}
 		}
 

+ 5 - 4
Terminal.Gui/Core/Responder.cs

@@ -56,7 +56,7 @@ namespace Terminal.Gui {
 		/// Gets or sets a value indicating whether this <see cref="Responder"/> has focus.
 		/// </summary>
 		/// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
-		public virtual bool HasFocus { get; internal set; }
+		public virtual bool HasFocus { get; }
 
 		// Key handling
 		/// <summary>
@@ -161,7 +161,6 @@ namespace Terminal.Gui {
 			return false;
 		}
 
-
 		/// <summary>
 		/// Method invoked when a mouse event is generated
 		/// </summary>
@@ -195,8 +194,9 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Method invoked when a view gets focus.
 		/// </summary>
+		/// <param name="view">The view that is losing focus.</param>
 		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public virtual bool OnEnter ()
+		public virtual bool OnEnter (View view)
 		{
 			return false;
 		}
@@ -204,8 +204,9 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Method invoked when a view loses focus.
 		/// </summary>
+		/// <param name="view">The view that is getting focus.</param>
 		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public virtual bool OnLeave ()
+		public virtual bool OnLeave (View view)
 		{
 			return false;
 		}

+ 69 - 28
Terminal.Gui/Core/View.cs

@@ -124,6 +124,16 @@ namespace Terminal.Gui {
 
 		TextFormatter textFormatter;
 
+		/// <summary>
+		/// Event fired when a subview is being added to this view.
+		/// </summary>
+		public Action<View> Added;
+
+		/// <summary>
+		/// Event fired when a subview is being removed from this view.
+		/// </summary>
+		public Action<View> Removed;
+
 		/// <summary>
 		/// Event fired when the view gets focus.
 		/// </summary>
@@ -552,6 +562,7 @@ namespace Terminal.Gui {
 				subviews = new List<View> ();
 			subviews.Add (view);
 			view.container = this;
+			OnAdded (view);
 			if (view.CanFocus)
 				CanFocus = true;
 			SetNeedsLayout ();
@@ -601,6 +612,7 @@ namespace Terminal.Gui {
 			var touched = view.Frame;
 			subviews.Remove (view);
 			view.container = null;
+			OnRemoved (view);
 
 			if (subviews.Count < 1)
 				this.CanFocus = false;
@@ -895,26 +907,31 @@ namespace Terminal.Gui {
 			}
 		}
 
+		bool hasFocus;
 		/// <inheritdoc/>
 		public override bool HasFocus {
 			get {
-				return base.HasFocus;
+				return hasFocus;
 			}
-			internal set {
-				if (base.HasFocus != value)
-					if (value)
-						OnEnter ();
-					else
-						OnLeave ();
-				SetNeedsDisplay ();
-				base.HasFocus = value;
+		}
 
-				// Remove focus down the chain of subviews if focus is removed
-				if (!value && focused != null) {
-					focused.OnLeave ();
-					focused.HasFocus = false;
-					focused = null;
-				}
+		void SetHasFocus (bool value, View view)
+		{
+			if (hasFocus != value) {
+				hasFocus = value;
+			}
+			if (value) {
+				OnEnter (view);
+			} else {
+				OnLeave (view);
+			}
+			SetNeedsDisplay ();
+
+			// Remove focus down the chain of subviews if focus is removed
+			if (!value && focused != null) {
+				focused.OnLeave (view);
+				focused.SetHasFocus (false, view);
+				focused = null;
 			}
 		}
 
@@ -925,35 +942,58 @@ namespace Terminal.Gui {
 			/// <summary>
 			/// Constructs.
 			/// </summary>
-			public FocusEventArgs () { }
+			/// <param name="view">The view that gets or loses focus.</param>
+			public FocusEventArgs (View view) { View = view; }
 			/// <summary>
 			/// Indicates if the current focus 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; }
+			/// <summary>
+			/// Indicates the current view that gets or loses focus.
+			/// </summary>
+			public View View { get; set; }
+		}
+
+		/// <summary>
+		/// Method invoked  when a subview is being added to this view.
+		/// </summary>
+		/// <param name="view">The subview being added.</param>
+		public virtual void OnAdded (View view)
+		{
+			view.Added?.Invoke (this);
+		}
+
+		/// <summary>
+		/// Method invoked when a subview is being removed from this view.
+		/// </summary>
+		/// <param name="view">The subview being removed.</param>
+		public virtual void OnRemoved (View view)
+		{
+			view.Removed?.Invoke (this);
 		}
 
 		/// <inheritdoc/>
-		public override bool OnEnter ()
+		public override bool OnEnter (View view)
 		{
-			FocusEventArgs args = new FocusEventArgs ();
+			FocusEventArgs args = new FocusEventArgs (view);
 			Enter?.Invoke (args);
 			if (args.Handled)
 				return true;
-			if (base.OnEnter ())
+			if (base.OnEnter (view))
 				return true;
 
 			return false;
 		}
 
 		/// <inheritdoc/>
-		public override bool OnLeave ()
+		public override bool OnLeave (View view)
 		{
-			FocusEventArgs args = new FocusEventArgs ();
+			FocusEventArgs args = new FocusEventArgs (view);
 			Leave?.Invoke (args);
 			if (args.Handled)
 				return true;
-			if (base.OnLeave ())
+			if (base.OnLeave (view))
 				return true;
 
 			return false;
@@ -1125,10 +1165,11 @@ namespace Terminal.Gui {
 				throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
 
 			if (focused != null)
-				focused.HasFocus = false;
+				focused.SetHasFocus (false, view);
 
+			var f = focused;
 			focused = view;
-			focused.HasFocus = true;
+			focused.SetHasFocus (true, f);
 			focused.EnsureFocus ();
 
 			// Send focus upwards
@@ -1322,7 +1363,7 @@ namespace Terminal.Gui {
 					continue;
 				}
 				if (w.CanFocus && focused_idx != -1) {
-					focused.HasFocus = false;
+					focused.SetHasFocus (false, w);
 
 					if (w != null && w.CanFocus)
 						w.FocusLast ();
@@ -1332,7 +1373,7 @@ namespace Terminal.Gui {
 				}
 			}
 			if (focused != null) {
-				focused.HasFocus = false;
+				focused.SetHasFocus (false, this);
 				focused = null;
 			}
 			return false;
@@ -1364,7 +1405,7 @@ namespace Terminal.Gui {
 					continue;
 				}
 				if (w.CanFocus && focused_idx != -1) {
-					focused.HasFocus = false;
+					focused.SetHasFocus (false, w);
 
 					if (w != null && w.CanFocus)
 						w.FocusFirst ();
@@ -1374,7 +1415,7 @@ namespace Terminal.Gui {
 				}
 			}
 			if (focused != null) {
-				focused.HasFocus = false;
+				focused.SetHasFocus (false, this);
 				focused = null;
 			}
 			return false;

+ 251 - 156
Terminal.Gui/Views/ComboBox.cs

@@ -4,15 +4,10 @@
 // Authors:
 //   Ross Ferguson ([email protected])
 //
-// TODO:
-//  LayoutComplete() resize Height implement
-//	Cursor rolls of end of list when Height = Dim.Fill() and list fills frame
-//
 
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.Linq;
 using NStack;
 
 namespace Terminal.Gui {
@@ -33,7 +28,12 @@ namespace Terminal.Gui {
 			get => source;
 			set {
 				source = value;
-				SetNeedsDisplay ();
+
+				// Only need to refresh list if its been added to a container view
+				if(SuperView != null && SuperView.Subviews.Contains(this)) { 
+					Search_Changed ("");
+					SetNeedsDisplay ();
+				}
 			}
 		}
 
@@ -46,34 +46,34 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public void SetSource (IList source)
 		{
-			if (source == null)
+			if (source == null) {
 				Source = null;
-			else {
-				Source = MakeWrapper (source);
+			} else {
+				listview.SetSource (source);
+				Source = listview.Source;
 			}
 		}
 
 		/// <summary>
-		///   Changed event, raised when the selection has been confirmed.
+		/// This event is raised when the selected item in the <see cref="ComboBox"/> has changed.
 		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the selection has been confirmed.
-		/// </remarks>
-		public event EventHandler<ustring> SelectedItemChanged;
+		public Action<ListViewItemEventArgs> SelectedItemChanged;
+
+		/// <summary>
+		/// This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item.
+		/// </summary>
+		public Action<ListViewItemEventArgs> OpenSelectedItem;
 
 		IList searchset;
 		ustring text = "";
 		readonly TextField search;
 		readonly ListView listview;
-		int height;
-		int width;
 		bool autoHide = true;
 
 		/// <summary>
 		/// Public constructor
 		/// </summary>
-		public ComboBox () : base()
+		public ComboBox () : base ()
 		{
 			search = new TextField ("");
 			listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true };
@@ -81,6 +81,19 @@ namespace Terminal.Gui {
 			Initialize ();
 		}
 
+		/// <summary>
+		/// Public constructor
+		/// </summary>
+		/// <param name="text"></param>
+		public ComboBox (ustring text) : base ()
+		{
+			search = new TextField ("");
+			listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true };
+						
+			Initialize ();
+			Text = text;
+		}
+
 		/// <summary>
 		/// Public constructor
 		/// </summary>
@@ -88,42 +101,45 @@ namespace Terminal.Gui {
 		/// <param name="source"></param>
 		public ComboBox (Rect rect, IList source) : base (rect)
 		{
-			SetSource (source);
-			this.height = rect.Height;
-			this.width = rect.Width;
-
-			search = new TextField ("") { Width = width };
-			listview = new ListView (rect, source) { LayoutStyle = LayoutStyle.Computed };
+			search = new TextField ("") { Width = rect.Width };
+			listview = new ListView (rect, source) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
 
 			Initialize ();
+			SetSource (source);
 		}
 
-		static IListDataSource MakeWrapper (IList source)
+		private void Initialize ()
 		{
-			return new ListWrapper (source);
-		}
+			search.TextChanged += Search_Changed;
+			search.MouseClick += Search_MouseClick;
 
-		private void Initialize()
-		{
-			ColorScheme = Colors.Base;
+			listview.Y = Pos.Bottom (search);
+			listview.OpenSelectedItem += (ListViewItemEventArgs a) => Selected ();
 
-			search.TextChanged += Search_Changed;
+			this.Add (listview, search);
+			this.SetFocus (search);
 
 			// On resize
 			LayoutComplete += (LayoutEventArgs a) => {
-
-				search.Width = Bounds.Width;
-				listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width;
+				if (!autoHide && search.Frame.Width != Bounds.Width ||
+					autoHide && search.Frame.Width != Bounds.Width - 1) {
+					search.Width = Bounds.Width;
+					listview.Width = listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width;
+					listview.Height = CalculatetHeight ();
+					search.SetRelativeLayout (Bounds);
+					listview.SetRelativeLayout (Bounds);
+				}
 			};
 
 			listview.SelectedItemChanged += (ListViewItemEventArgs e) => {
 
-				if(searchset.Count > 0)
-					SetValue ((ustring)searchset [listview.SelectedItem]);
+				if (searchset.Count > 0) {
+					SetValue (searchset [listview.SelectedItem]);
+				}
 			};
 
-#if false
-			Application.Loaded += (Application.ResizedEventArgs a) => {
+			Added += (View v) => {
+
 				// Determine if this view is hosted inside a dialog
 				for (View view = this.SuperView; view != null; view = view.SuperView) {
 					if (view is Dialog) {
@@ -132,63 +148,60 @@ namespace Terminal.Gui {
 					}
 				}
 
-				ResetSearchSet ();
-
-				ColorScheme = autoHide ? Colors.Base : ColorScheme = null;
-
-				// Needs to be re-applied for LayoutStyle.Computed
-				// If Dim or Pos are null, these are the from the parametrized constructor
-				listview.Y = 1;
-
-				if (Width == null) {
-					listview.Width = CalculateWidth ();
-					search.Width = width;
-				} else {
-					width = GetDimAsInt (Width, vertical: false);
-					search.Width = width;
-					listview.Width = CalculateWidth ();
-				}
-
-				if (Height == null) {
-					var h = CalculatetHeight ();
-					listview.Height = h;
-					this.Height = h + 1; // adjust view to account for search box
-				} else {
-					if (height == 0)
-						height = GetDimAsInt (Height, vertical: true);
-
-					listview.Height = CalculatetHeight ();
-					this.Height = height + 1; // adjust view to account for search box
-				}
+				SetNeedsLayout ();
+				SetNeedsDisplay ();
+				Search_Changed (Text);
+			};
+		}
 
-				if (this.Text != null)
-					Search_Changed (Text);
+		/// <summary>
+		/// Gets the index of the currently selected item in the <see cref="Source"/>
+		/// </summary>
+		/// <value>The selected item or -1 none selected.</value>
+		public int SelectedItem { private set; get; }
 
-				if (autoHide)
-					listview.ColorScheme = Colors.Menu;
-				else
-					search.ColorScheme = Colors.Menu;
-			};
-#endif
-			search.MouseClick += Search_MouseClick;
+		bool isShow = false;
 
-			this.Add(listview, search);
-			this.SetFocus(search);
+		///<inheritdoc/>
+		public new ColorScheme ColorScheme {
+			get {
+				return base.ColorScheme;
+			}
+			set {
+				listview.ColorScheme = value;			
+				base.ColorScheme = value;
+				SetNeedsDisplay ();
+			}
 		}
 
-		private void Search_MouseClick (MouseEventArgs e)
+		private void Search_MouseClick (MouseEventArgs me)
 		{
-			if (e.MouseEvent.Flags != MouseFlags.Button1Clicked)
-				return;
+			if (me.MouseEvent.X == Bounds.Right - 1 && me.MouseEvent.Y == Bounds.Top && me.MouseEvent.Flags == MouseFlags.Button1Pressed
+			&& search.Text == "" && autoHide) {
 
-			SuperView.SetFocus (search);
+				if (isShow) {
+					HideList ();
+					isShow = false;
+				} else {
+					// force deep copy
+					foreach (var item in Source.ToList()) { 
+						searchset.Add (item);
+					}
+
+					ShowList ();
+					isShow = true;
+				}
+			} else { 
+				SuperView.SetFocus (search);
+			}
 		}
 
 		///<inheritdoc/>
-		public override bool OnEnter ()
+		public override bool OnEnter (View view)
 		{
-			if (!search.HasFocus)
+			if (!search.HasFocus) {
 				this.SetFocus (search);
+			}
 
 			search.CursorPosition = search.Text.RuneCount;
 
@@ -203,46 +216,64 @@ namespace Terminal.Gui {
 		{
 			// Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. 
 			// So we cannot optimize. Ie: Don't call if not changed
-			SelectedItemChanged?.Invoke (this, search.Text);
+			SelectedItemChanged?.Invoke (new ListViewItemEventArgs(SelectedItem, search.Text));
+
+			return true;
+		}
+
+		/// <summary>
+		/// Invokes the OnOpenSelectedItem event if it is defined.
+		/// </summary>
+		/// <returns></returns>
+		public virtual bool OnOpenSelectedItem ()
+		{
+			var value = search.Text;
+			OpenSelectedItem?.Invoke (new ListViewItemEventArgs (SelectedItem, value));
 
 			return true;
 		}
 
 		///<inheritdoc/>
-		public override bool ProcessKey(KeyEvent e)
+		public override void Redraw (Rect bounds)
 		{
-			if (e.Key == Key.Tab) {
-				base.ProcessKey(e);
-				return false; // allow tab-out to next control
+			base.Redraw (bounds);
+
+			if (!autoHide) {
+				return;
 			}
 
-			if (e.Key == Key.Enter && listview.HasFocus) {
-				if (listview.Source.Count == 0 || searchset.Count == 0) {
-					text = "";
-					return true;
-				}
+			Move (Bounds.Right - 1, 0);
+			Driver.AddRune (Driver.DownArrow);
+		}
 
-				SetValue((ustring)searchset [listview.SelectedItem]);
-				search.CursorPosition = search.Text.RuneCount;
-				Search_Changed (search.Text);
-				OnSelectedChanged ();
+		///<inheritdoc/>
+		public override bool ProcessKey (KeyEvent e)
+		{
+			if (e.Key == Key.Tab) {
+				base.ProcessKey (e);
+				return false; // allow tab-out to next control
+			}
 
-				searchset.Clear();
-				listview.Clear ();
-				listview.Height = 0;
-				this.SetFocus(search);
+			if(e.Key == Key.BackTab) {
+				base.ProcessKey (e);
+				this.FocusPrev ();
+				return false; // allow tab-out to prev control
+			}
 
+			if (e.Key == Key.Enter && listview.HasFocus) {
+				Selected ();
 				return true;
 			}
 
-			if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) { // jump to list
+			if (e.Key == Key.CursorDown && search.HasFocus && searchset.Count > 0) { // jump to list
 				this.SetFocus (listview);
-				SetValue ((ustring)searchset [listview.SelectedItem]);
+				SetValue (searchset [listview.SelectedItem]);
 				return true;
 			}
 
-			if (e.Key == Key.CursorUp && search.HasFocus) // stop odd behavior on KeyUp when search has focus
+			if (e.Key == Key.CursorUp && search.HasFocus) { // stop odd behavior on KeyUp when search has focus
 				return true;
+			}
 
 			if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) // jump back to search
 			{
@@ -251,6 +282,34 @@ namespace Terminal.Gui {
 				return true;
 			}
 
+			if(e.Key == Key.PageDown) {
+				if (listview.SelectedItem != -1) {
+					listview.MovePageDown ();
+				}
+				return true;
+			}
+
+			if (e.Key == Key.PageUp) {
+				if (listview.SelectedItem != -1) {
+					listview.MovePageUp ();
+				}
+				return true;
+			}
+
+			if (e.Key == Key.Home) {
+				if (listview.SelectedItem != -1) {
+					listview.MoveHome ();				
+				}
+				return true;
+			}
+
+			if(e.Key == Key.End) {
+				if(listview.SelectedItem != -1) { 
+					listview.MoveEnd ();
+				}
+				return true;
+			}
+
 			if (e.Key == Key.Esc) {
 				this.SetFocus (search);
 				search.Text = text = "";
@@ -259,22 +318,19 @@ namespace Terminal.Gui {
 			}
 
 			// Unix emulation
-			if (e.Key == Key.ControlU)
-			{
-				Reset();
+			if (e.Key == Key.ControlU) {
+				Reset ();
 				return true;
 			}
 
-			return base.ProcessKey(e);
+			return base.ProcessKey (e);
 		}
 
 		/// <summary>
 		/// The currently selected list item
 		/// </summary>
-		public new ustring Text
-		{
-			get
-			{
+		public new ustring Text {
+			get {
 				return text;
 			}
 			set {
@@ -282,92 +338,131 @@ namespace Terminal.Gui {
 			}
 		}
 
-		private void SetValue(ustring text)
+		private void SetValue (object text)
 		{
 			search.TextChanged -= Search_Changed;
-			this.text = search.Text = text;
+			this.text = search.Text = text.ToString();
 			search.CursorPosition = 0;
 			search.TextChanged += Search_Changed;
+			SelectedItem = GetSelectedItemFromSource (this.text);
+			OnSelectedChanged ();
+		}
+
+		private void Selected ()
+		{
+			if (listview.Source.Count == 0 || searchset.Count == 0) {
+				text = "";
+				return;
+			}
+
+			SetValue (searchset [listview.SelectedItem]);
+			search.CursorPosition = search.Text.RuneCount;
+			Search_Changed (search.Text);
+			OnOpenSelectedItem ();
+			Reset (keepSearchText: true);
+		}
+
+		private int GetSelectedItemFromSource (ustring value)
+		{
+			if (source == null) {
+				return -1;
+			}
+			for (int i = 0; i < source.Count; i++) {
+				if (source.ToList () [i].ToString () == value) {
+					return i;
+				}
+			}
+			return -1;
 		}
 
 		/// <summary>
 		/// Reset to full original list
 		/// </summary>
-		private void Reset()
+		private void Reset (bool keepSearchText = false)
 		{
-			search.Text = text = "";
-			OnSelectedChanged();
+			if (!keepSearchText) {
+				search.Text = text = "";
+			}
 
 			ResetSearchSet ();
 
-			listview.SetSource(searchset);
+			listview.SetSource (searchset);
 			listview.Height = CalculatetHeight ();
 
-			this.SetFocus(search);
+			this.SetFocus (search);
 		}
 
-		private void ResetSearchSet()
+		private void ResetSearchSet (bool noCopy = false)
 		{
-			if (autoHide) {
-				if (searchset == null)
-					searchset = new List<string> ();
-				else
-					searchset.Clear ();
-			} else
-				searchset = source.ToList ();
+			if (searchset == null) {
+				searchset = new List<object> ();
+			} else { 
+				searchset.Clear ();
+			}
+
+			if (autoHide || noCopy)
+				return;
+
+			// force deep copy
+			foreach (var item in Source.ToList ()) {
+				searchset.Add (item);
+			}
 		}
 
 		private void Search_Changed (ustring text)
 		{
-			if (source == null) // Object initialization
+			if (source == null) { // Object initialization		
 				return;
+			}
 
-			if (ustring.IsNullOrEmpty (search.Text))
+			if (ustring.IsNullOrEmpty (search.Text)) {
 				ResetSearchSet ();
-			else
-				searchset = source.ToList().Cast<ustring>().Where (x => x.StartsWith (search.Text)).ToList();
+			} else {
+				ResetSearchSet (noCopy: true);
 
-			listview.SetSource (searchset);
-			listview.Height = CalculatetHeight ();
+				foreach (var item in source.ToList ()) { // Iterate to preserver object type and force deep copy
+					if (item.ToString().StartsWith (search.Text.ToString(), StringComparison.CurrentCultureIgnoreCase)) { 
+						searchset.Add (item);
+					}
+				}
+			}
 
-			listview.Redraw (new Rect (0, 0, width, height)); // for any view behind this
-			this.SuperView?.BringSubviewToFront (this);
+			ShowList ();
 		}
 
 		/// <summary>
-		/// Internal height of dynamic search list
+		/// Show the search list
 		/// </summary>
-		/// <returns></returns>
-		private int CalculatetHeight ()
+		/// 
+		/// Consider making public
+		private void ShowList ()
 		{
-			return Math.Min (height, searchset.Count);
+			listview.SetSource (searchset);
+			listview.Clear (); // Ensure list shrinks in Dialog as you type
+			listview.Height = CalculatetHeight ();
+			this.SuperView?.BringSubviewToFront (this);
 		}
 
 		/// <summary>
-		/// Internal width of search list
+		/// Hide the search list
 		/// </summary>
-		/// <returns></returns>
-		private int CalculateWidth ()
+		/// 
+		/// Consider making public
+		private void HideList ()
 		{
-			return autoHide ? Math.Max (1, width - 1) : width;
+			Reset ();
 		}
 
 		/// <summary>
-		/// Get Dim as integer value
+		/// Internal height of dynamic search list
 		/// </summary>
-		/// <param name="dim"></param>
-		/// <param name="vertical"></param>
-		/// <returns></returns>n
-		private int GetDimAsInt (Dim dim, bool vertical)
+		/// <returns></returns>
+		private int CalculatetHeight ()
 		{
-			if (dim is Dim.DimAbsolute)
-				return dim.Anchor (0);
-			else { // Dim.Fill Dim.Factor
-				if(autoHide)
-					return vertical ? dim.Anchor (SuperView.Bounds.Height) : dim.Anchor (SuperView.Bounds.Width);
-				else 
-					return vertical ? dim.Anchor (Bounds.Height) : dim.Anchor (Bounds.Width);
-			}
+			if (Bounds.Height == 0)
+				return 0;
+
+			return Math.Min (Bounds.Height - 1, searchset?.Count ?? 0);
 		}
 	}
 }

+ 7 - 5
Terminal.Gui/Views/ListView.cs

@@ -589,15 +589,17 @@ namespace Terminal.Gui {
 		/// <param name="source"></param>
 		public ListWrapper (IList source)
 		{
-			count = source.Count;
-			marks = new BitArray (count);
-			this.src = source;
+			if (source != null) {
+				count = source.Count;
+				marks = new BitArray (count);
+				this.src = source;
+			}
 		}
 
 		/// <summary>
 		/// Gets the number of items in the <see cref="IList"/>.
 		/// </summary>
-		public int Count => src.Count;
+		public int Count => src != null ? src.Count : 0;
 
 		void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
 		{
@@ -632,7 +634,7 @@ namespace Terminal.Gui {
 			container.Move (col, line);
 			var t = src [item];
 			if (t == null) {
-				RenderUstr (driver, ustring.Make(""), col, line, width);
+				RenderUstr (driver, ustring.Make (""), col, line, width);
 			} else {
 				if (t is ustring) {
 					RenderUstr (driver, (ustring)t, col, line, width);

+ 0 - 2
Terminal.Gui/Views/Menu.cs

@@ -535,8 +535,6 @@ namespace Terminal.Gui {
 				if (item == null || !item.IsEnabled ()) disabled = true;
 				if (item != null && !disabled)
 					current = me.Y - 1;
-				HasFocus = true;
-				SetNeedsDisplay ();
 				CheckSubMenu ();
 				return true;
 			}

+ 2 - 2
Terminal.Gui/Views/TextField.cs

@@ -93,14 +93,14 @@ namespace Terminal.Gui {
 		}
 
 		///<inheritdoc/>
-		public override bool OnLeave ()
+		public override bool OnLeave (View view)
 		{
 			if (Application.mouseGrabView != null && Application.mouseGrabView == this)
 				Application.UngrabMouse ();
 			if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar))
 				ClearAllSelection ();
 
-			return base.OnLeave ();
+			return base.OnLeave (view);
 		}
 
 		///<inheritdoc/>

+ 1 - 1
UICatalog/Scenarios/AllViewsTester.cs

@@ -372,7 +372,7 @@ namespace UICatalog {
 
 			// If the view supports a Source property, set it so we have something to look at
 			if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType().GetProperty("Source").PropertyType == typeof(Terminal.Gui.IListDataSource)) {
-				var source = new ListWrapper (new List<ustring> () { ustring.Make ("List Item #1"), ustring.Make ("List Item #2"), ustring.Make ("List Item #3")});
+				var source = new ListWrapper (new List<ustring> () { ustring.Make ("Test Text #1"), ustring.Make ("Test Text #2"), ustring.Make ("Test Text #3") });
 				view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
 			}
 

+ 10 - 9
UICatalog/Scenarios/ListsAndCombos.cs

@@ -12,13 +12,14 @@ namespace UICatalog.Scenarios {
 
 		public override void Setup ()
 		{
-			List<string> items = new List<string> ();
-			foreach (var dir in new [] { "/etc", @"\windows\System32" }) {
+			//TODO: Duplicated code in Demo.cs Consider moving to shared assembly
+			var items = new List<ustring> ();
+			foreach (var dir in new [] { "/etc", @$"{Environment.GetEnvironmentVariable ("SystemRoot")}\System32" }) {
 				if (Directory.Exists (dir)) {
 					items = Directory.GetFiles (dir).Union(Directory.GetDirectories(dir))
-					.Select (Path.GetFileName)
-					.Where (x => char.IsLetterOrDigit (x [0]))
-					.OrderBy (x => x).ToList ();
+						.Select (Path.GetFileName)
+						.Where (x => char.IsLetterOrDigit (x [0]))
+						.OrderBy (x => x).Select(x => ustring.Make(x)).ToList() ;
 				}
 			}
 
@@ -26,16 +27,16 @@ namespace UICatalog.Scenarios {
 			var lbListView = new Label ("Listview") {
 				ColorScheme = Colors.TopLevel,
 				X = 0,
-				Width = 30
+				Width = Dim.Percent (40)
 			};
 
 			var listview = new ListView (items) {
 				X = 0,
 				Y = Pos.Bottom (lbListView) + 1,
 				Height = Dim.Fill(2),
-				Width = 30
+				Width = Dim.Percent (40)
 			};
-			listview.OpenSelectedItem += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
+			listview.SelectedItemChanged += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
 			Win.Add (lbListView, listview);
 
 			// ComboBox
@@ -53,7 +54,7 @@ namespace UICatalog.Scenarios {
 			};
 			comboBox.SetSource (items);
 
-			comboBox.SelectedItemChanged += (object sender, ustring text) => lbComboBox.Text = text;
+			comboBox.SelectedItemChanged += (ListViewItemEventArgs text) => lbComboBox.Text = items[comboBox.SelectedItem];
 			Win.Add (lbComboBox, comboBox);
 		}
 	}

+ 2 - 2
UnitTests/ResponderTests.cs

@@ -32,8 +32,8 @@ namespace Terminal.Gui {
 			Assert.False (r.MouseEvent (new MouseEvent () { Flags = MouseFlags.AllEvents }));
 			Assert.False (r.OnMouseEnter (new MouseEvent () { Flags = MouseFlags.AllEvents }));
 			Assert.False (r.OnMouseLeave (new MouseEvent () { Flags = MouseFlags.AllEvents }));
-			Assert.False (r.OnEnter ());
-			Assert.False (r.OnLeave ());
+			Assert.False (r.OnEnter (new View ()));
+			Assert.False (r.OnLeave (new View ()));
 		}
 	}
 }

+ 23 - 2
UnitTests/ViewTests.cs

@@ -102,8 +102,8 @@ namespace Terminal.Gui {
 			Assert.False (r.MouseEvent (new MouseEvent () { Flags = MouseFlags.AllEvents }));
 			Assert.False (r.OnMouseEnter (new MouseEvent () { Flags = MouseFlags.AllEvents }));
 			Assert.False (r.OnMouseLeave (new MouseEvent () { Flags = MouseFlags.AllEvents }));
-			Assert.False (r.OnEnter ());
-			Assert.False (r.OnLeave ());
+			Assert.False (r.OnEnter (new View ()));
+			Assert.False (r.OnLeave (new View ()));
 
 			// TODO: Add more
 		}
@@ -135,5 +135,26 @@ namespace Terminal.Gui {
 			sub2.Width = Dim.Width (sub2);
 			Assert.Throws<InvalidOperationException> (() => root.LayoutSubviews ());
 		}
+
+		[Fact]
+		public void Added_Removing ()
+		{
+			var v = new View (new Rect (0, 0, 10, 24));
+			var t = new View ();
+
+			v.Added += (View e) => {
+				Assert.True (v.SuperView == e);
+			};
+
+			v.Removed += (View e) => {
+				Assert.True (v.SuperView == null);
+			};
+
+			t.Add (v);
+			Assert.True (t.Subviews.Count == 1);
+
+			t.Remove (v);
+			Assert.True (t.Subviews.Count == 0);
+		}
 	}
 }