using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace Terminal.Gui { /// /// Toplevel views can be modally executed. They are used for both an application's main view (filling the entire screeN and /// for pop-up views such as , , and . /// /// /// /// Toplevels can be modally executing views, started by calling . /// They return control to the caller when has /// been called (which sets the property to false). /// /// /// A Toplevel is created when an application initializes Terminal.Gui by calling . /// The application Toplevel can be accessed via . Additional Toplevels can be created /// and run (e.g. s. To run a Toplevel, create the and /// call . /// /// /// Toplevels can also opt-in to more sophisticated initialization /// by implementing . When they do /// so, the and /// methods will be called /// before running the view. /// If first-run-only initialization is preferred, the /// can be implemented too, in which case the /// methods will only be called if /// is . This allows proper inheritance hierarchies /// to override base class layout code optimally by doing so only on first run, /// instead of on every run. /// /// public class Toplevel : View { /// /// Gets or sets whether the for this is running or not. /// /// /// Setting this property directly is discouraged. Use instead. /// public bool Running { get; set; } /// /// Invoked when the Toplevel has begun to be loaded. /// A Loaded event handler is a good place to finalize initialization before calling /// . /// public event Action Loaded; /// /// Invoked when the Toplevel has started it's first iteration. /// Subscribe to this event to perform tasks when the has been laid out and focus has been set. /// changes. /// A Ready event handler is a good place to finalize initialization after calling /// on this Toplevel. /// public event Action Ready; /// /// Invoked when the Toplevel has been unloaded. /// A Unloaded event handler is a good place to dispose objects after calling . /// public event Action Unloaded; /// /// Invoked when the Toplevel becomes the Toplevel. /// public event Action Activate; /// /// Invoked when the Toplevel ceases to be the Toplevel. /// public event Action Deactivate; /// /// Invoked when a child of the Toplevel is closed by /// . /// public event Action ChildClosed; /// /// Invoked when the last child of the Toplevel is closed from /// by . /// public event Action AllChildClosed; /// /// Invoked when the Toplevel's is being closed by /// . /// public event Action Closing; /// /// Invoked when the Toplevel's is closed by . /// public event Action Closed; /// /// Invoked when a child Toplevel's has been loaded. /// public event Action ChildLoaded; /// /// Invoked when a cjhild Toplevel's has been unloaded. /// public event Action ChildUnloaded; /// /// Invoked when the terminal has been resized. The new of the terminal is provided. /// public event Action Resized; internal virtual void OnResized (Size size) { Resized?.Invoke (size); } internal virtual void OnChildUnloaded (Toplevel top) { ChildUnloaded?.Invoke (top); } internal virtual void OnChildLoaded (Toplevel top) { ChildLoaded?.Invoke (top); } internal virtual void OnClosed (Toplevel top) { Closed?.Invoke (top); } internal virtual bool OnClosing (ToplevelClosingEventArgs ev) { Closing?.Invoke (ev); return ev.Cancel; } internal virtual void OnAllChildClosed () { AllChildClosed?.Invoke (); } internal virtual void OnChildClosed (Toplevel top) { if (IsMdiContainer) { SetChildNeedsDisplay (); } ChildClosed?.Invoke (top); } internal virtual void OnDeactivate (Toplevel activated) { Deactivate?.Invoke (activated); } internal virtual void OnActivate (Toplevel deactivated) { Activate?.Invoke (deactivated); } /// /// Called from before the redraws for the first time. /// virtual public void OnLoaded () { foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { tl.OnLoaded (); } Loaded?.Invoke (); } /// /// Called from after the has entered the /// first iteration of the loop. /// internal virtual void OnReady () { foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { tl.OnReady (); } Ready?.Invoke (); } /// /// Called from before the is disposed. /// internal virtual void OnUnloaded () { foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { tl.OnUnloaded (); } Unloaded?.Invoke (); } /// /// Initializes a new instance of the class with the specified layout. /// /// A superview-relative rectangle specifying the location and size for the new Toplevel public Toplevel (Rect frame) : base (frame) { Initialize (); } /// /// Initializes a new instance of the class with layout, /// defaulting to full screen. /// public Toplevel () : base () { Initialize (); Width = Dim.Fill (); Height = Dim.Fill (); } void Initialize () { ColorScheme = Colors.TopLevel; // Things this view knows how to do AddCommand (Command.QuitToplevel, () => { QuitToplevel (); return true; }); AddCommand (Command.Suspend, () => { Driver.Suspend (); ; return true; }); AddCommand (Command.NextView, () => { MoveNextView (); return true; }); AddCommand (Command.PreviousView, () => { MovePreviousView (); return true; }); AddCommand (Command.NextViewOrTop, () => { MoveNextViewOrTop (); return true; }); AddCommand (Command.PreviousViewOrTop, () => { MovePreviousViewOrTop (); return true; }); AddCommand (Command.Refresh, () => { Application.Refresh (); return true; }); // Default keybindings for this view AddKeyBinding (Application.QuitKey, Command.QuitToplevel); AddKeyBinding (Key.Z | Key.CtrlMask, Command.Suspend); AddKeyBinding (Key.Tab, Command.NextView); AddKeyBinding (Key.CursorRight, Command.NextView); AddKeyBinding (Key.F | Key.CtrlMask, Command.NextView); AddKeyBinding (Key.CursorDown, Command.NextView); AddKeyBinding (Key.I | Key.CtrlMask, Command.NextView); // Unix AddKeyBinding (Key.BackTab | Key.ShiftMask, Command.PreviousView); AddKeyBinding (Key.CursorLeft, Command.PreviousView); AddKeyBinding (Key.CursorUp, Command.PreviousView); AddKeyBinding (Key.B | Key.CtrlMask, Command.PreviousView); AddKeyBinding (Key.Tab | Key.CtrlMask, Command.NextViewOrTop); AddKeyBinding (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix AddKeyBinding (Key.Tab | Key.ShiftMask | Key.CtrlMask, Command.PreviousViewOrTop); AddKeyBinding (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix AddKeyBinding (Key.L | Key.CtrlMask, Command.Refresh); } /// /// Invoked when the is changed. /// public event Action AlternateForwardKeyChanged; /// /// Virtual method to invoke the event. /// /// public virtual void OnAlternateForwardKeyChanged (Key oldKey) { ReplaceKeyBinding (oldKey, Application.AlternateForwardKey); AlternateForwardKeyChanged?.Invoke (oldKey); } /// /// Invoked when the is changed. /// public event Action AlternateBackwardKeyChanged; /// /// Virtual method to invoke the event. /// /// public virtual void OnAlternateBackwardKeyChanged (Key oldKey) { ReplaceKeyBinding (oldKey, Application.AlternateBackwardKey); AlternateBackwardKeyChanged?.Invoke (oldKey); } /// /// Invoked when the is changed. /// public event Action QuitKeyChanged; /// /// Virtual method to invoke the event. /// /// public virtual void OnQuitKeyChanged (Key oldKey) { ReplaceKeyBinding (oldKey, Application.QuitKey); QuitKeyChanged?.Invoke (oldKey); } /// /// Convenience factory method that creates a new Toplevel with the current terminal dimensions. /// /// The created Toplevel. public static Toplevel Create () { return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows)); } /// /// Gets or sets a value indicating whether this can focus. /// /// true if can focus; otherwise, false. public override bool CanFocus { get => SuperView == null ? true : base.CanFocus; } /// /// Determines whether the is modal or not. /// If set to false (the default): /// /// /// /// events will propagate keys upwards. /// /// /// The Toplevel will act as an embedded view (not a modal/pop-up). /// /// /// /// If set to true: /// /// /// /// events will NOT propogate keys upwards. /// /// /// The Toplevel will and look like a modal (pop-up) (e.g. see . /// /// /// public bool Modal { get; set; } /// /// Gets or sets the menu for this Toplevel. /// public virtual MenuBar MenuBar { get; set; } /// /// Gets or sets the status bar for this Toplevel. /// public virtual StatusBar StatusBar { get; set; } /// /// Gets or sets if this Toplevel is a Mdi container. /// public bool IsMdiContainer { get; set; } /// /// Gets or sets if this Toplevel is a Mdi child. /// public bool IsMdiChild { get { return Application.MdiTop != null && Application.MdiTop != this && !Modal; } } /// public override bool OnKeyDown (KeyEvent keyEvent) { if (base.OnKeyDown (keyEvent)) { return true; } switch (keyEvent.Key) { case Key.AltMask: case Key.AltMask | Key.Space: case Key.CtrlMask | Key.Space: case Key _ when (keyEvent.Key & Key.AltMask) == Key.AltMask: return MenuBar != null && MenuBar.OnKeyDown (keyEvent); } return false; } /// public override bool OnKeyUp (KeyEvent keyEvent) { if (base.OnKeyUp (keyEvent)) { return true; } switch (keyEvent.Key) { case Key.AltMask: case Key.AltMask | Key.Space: case Key.CtrlMask | Key.Space: if (MenuBar != null && MenuBar.OnKeyUp (keyEvent)) { return true; } break; } return false; } /// public override bool ProcessKey (KeyEvent keyEvent) { if (base.ProcessKey (keyEvent)) return true; var result = InvokeKeybindings (new KeyEvent (ShortcutHelper.GetModifiersKey (keyEvent), new KeyModifiers () { Alt = keyEvent.IsAlt, Ctrl = keyEvent.IsCtrl, Shift = keyEvent.IsShift })); if (result != null) return (bool)result; #if false if (keyEvent.Key == Key.F5) { Application.DebugDrawBounds = !Application.DebugDrawBounds; SetNeedsDisplay (); return true; } #endif return false; } private void MovePreviousViewOrTop () { if (Application.MdiTop == null) { var top = Modal ? this : Application.Top; top.FocusPrev (); if (top.Focused == null) { top.FocusPrev (); } top.SetNeedsDisplay (); Application.EnsuresTopOnFront (); } else { MovePrevious (); } } private void MoveNextViewOrTop () { if (Application.MdiTop == null) { var top = Modal ? this : Application.Top; top.FocusNext (); if (top.Focused == null) { top.FocusNext (); } top.SetNeedsDisplay (); Application.EnsuresTopOnFront (); } else { MoveNext (); } } private void MovePreviousView () { var old = GetDeepestFocusedSubview (Focused); if (!FocusPrev ()) FocusPrev (); if (old != Focused && old != Focused?.Focused) { old?.SetNeedsDisplay (); Focused?.SetNeedsDisplay (); } else { FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward); } } private void MoveNextView () { var old = GetDeepestFocusedSubview (Focused); if (!FocusNext ()) FocusNext (); if (old != Focused && old != Focused?.Focused) { old?.SetNeedsDisplay (); Focused?.SetNeedsDisplay (); } else { FocusNearestView (SuperView?.TabIndexes, Direction.Forward); } } private void QuitToplevel () { if (Application.MdiTop != null) { Application.MdiTop.RequestStop (); } else { Application.RequestStop (); } } /// public override bool ProcessColdKey (KeyEvent keyEvent) { if (base.ProcessColdKey (keyEvent)) { return true; } if (ShortcutHelper.FindAndOpenByShortcut (keyEvent, this)) { return true; } return false; } View GetDeepestFocusedSubview (View view) { if (view == null) { return null; } foreach (var v in view.Subviews) { if (v.HasFocus) { return GetDeepestFocusedSubview (v); } } return view; } void FocusNearestView (IEnumerable views, Direction direction) { if (views == null) { return; } bool found = false; bool focusProcessed = false; int idx = 0; foreach (var v in views) { if (v == this) { found = true; } if (found && v != this) { if (direction == Direction.Forward) { SuperView?.FocusNext (); } else { SuperView?.FocusPrev (); } focusProcessed = true; if (SuperView.Focused != null && SuperView.Focused != this) { return; } } else if (found && !focusProcessed && idx == views.Count () - 1) { views.ToList () [0].SetFocus (); } idx++; } } /// public override void Add (View view) { AddMenuStatusBar (view); base.Add (view); } internal void AddMenuStatusBar (View view) { if (view is MenuBar) { MenuBar = view as MenuBar; } if (view is StatusBar) { StatusBar = view as StatusBar; } } /// public override void Remove (View view) { if (this is Toplevel toplevel && toplevel.MenuBar != null) { RemoveMenuStatusBar (view); } base.Remove (view); } /// public override void RemoveAll () { if (this == Application.Top) { MenuBar?.Dispose (); MenuBar = null; StatusBar?.Dispose (); StatusBar = null; } base.RemoveAll (); } internal void RemoveMenuStatusBar (View view) { if (view is MenuBar) { MenuBar?.Dispose (); MenuBar = null; } if (view is StatusBar) { StatusBar?.Dispose (); StatusBar = null; } } internal View EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny, out View mb, out View sb) { int l; View superView = null; if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { l = Driver.Cols; superView = Application.Top; } else { l = top.SuperView.Frame.Width; superView = top.SuperView; } nx = Math.Max (x, 0); nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx; var mfLength = top.Border?.DrawMarginFrame == true ? 2 : 1; if (nx + mfLength > top.Frame.X + top.Frame.Width) { nx = Math.Max (top.Frame.Right - mfLength, 0); } //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); bool m = false, s = false; mb = null; sb = null; if (!(top is Window && top == Application.Top) && (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top)) { m = Application.Top.MenuBar?.Visible == true; mb = Application.Top.MenuBar; } else if (!(top is Window && top == Application.Top)) { var t = top.SuperView; while (!(t is Toplevel)) { t = t.SuperView; } m = ((Toplevel)t).MenuBar?.Visible == true; mb = ((Toplevel)t).MenuBar; } if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { l = m ? 1 : 0; } else { l = 0; } ny = Math.Max (y, l); if (!(top is Window && top == Application.Top) && (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top)) { s = Application.Top.StatusBar?.Visible == true; sb = Application.Top.StatusBar; } else if (!(top is Window && top == Application.Top)) { var t = top.SuperView; while (!(t is Toplevel)) { t = t.SuperView; } s = ((Toplevel)t).StatusBar?.Visible == true; sb = ((Toplevel)t).StatusBar; } if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { l = s ? Driver.Rows - 1 : Driver.Rows; } else { l = s ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height; } ny = Math.Min (ny, l); ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; if (ny + mfLength > top.Frame.Y + top.Frame.Height) { ny = Math.Max (top.Frame.Bottom - mfLength, 0); } //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); return superView; } internal void PositionToplevels () { PositionToplevel (this); foreach (var top in Subviews) { if (top is Toplevel) { PositionToplevel ((Toplevel)top); } } } /// /// Virtual method enabling implementation of specific positions for inherited views. /// /// The toplevel. public virtual void PositionToplevel (Toplevel top) { var superView = EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny, out _, out View sb); bool layoutSubviews = false; if ((top?.SuperView != null || (top != Application.Top && top.Modal) || (top?.SuperView == null && top.IsMdiChild)) && (nx > top.Frame.X || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { if ((top.X == null || top.X is Pos.PosAbsolute) && top.Bounds.X != nx) { top.X = nx; layoutSubviews = true; } if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Bounds.Y != ny) { top.Y = ny; layoutSubviews = true; } } if (sb != null && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) && top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) { top.Height = Dim.Fill (sb.Visible ? 1 : 0); layoutSubviews = true; } if (layoutSubviews) { superView.LayoutSubviews (); } } /// public override void Redraw (Rect bounds) { if (!Visible) { return; } if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) { Driver.SetAttribute (GetNormalColor ()); // This is the Application.Top. Clear just the region we're being asked to redraw // (the bounds passed to us). Clear (); Driver.SetAttribute (Enabled ? Colors.Base.Normal : Colors.Base.Disabled); LayoutSubviews (); PositionToplevels (); if (this == Application.MdiTop) { foreach (var top in Application.MdiChildes.AsEnumerable ().Reverse ()) { if (top.Frame.IntersectsWith (bounds)) { if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) { top.SetNeedsLayout (); top.SetNeedsDisplay (top.Bounds); top.Redraw (top.Bounds); } } } } foreach (var view in Subviews) { if (view.Frame.IntersectsWith (bounds) && !OutsideTopFrame (this)) { view.SetNeedsLayout (); view.SetNeedsDisplay (view.Bounds); //view.Redraw (view.Bounds); } } ClearLayoutNeeded (); ClearNeedsDisplay (); } base.Redraw (Bounds); } bool OutsideTopFrame (Toplevel top) { if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) { return true; } return false; } internal static Point? dragPosition; Point start; /// public override bool MouseEvent (MouseEvent mouseEvent) { if (!CanFocus) { return true; } //System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}"); int nx, ny; if (!dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed || mouseEvent.Flags == MouseFlags.Button2Pressed || mouseEvent.Flags == MouseFlags.Button3Pressed)) { SetFocus (); Application.EnsuresTopOnFront (); // Only start grabbing if the user clicks on the title bar. if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) { start = new Point (mouseEvent.X, mouseEvent.Y); dragPosition = new Point (); nx = mouseEvent.X - mouseEvent.OfX; ny = mouseEvent.Y - mouseEvent.OfY; dragPosition = new Point (nx, ny); Application.GrabMouse (this); } //System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}"); return true; } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || mouseEvent.Flags == MouseFlags.Button3Pressed) { if (dragPosition.HasValue) { if (SuperView == null) { // Redraw the entire app window using just our Frame. Since we are // Application.Top, and our Frame always == our Bounds (Location is always (0,0)) // our Frame is actually view-relative (which is what Redraw takes). // We need to pass all the view bounds because since the windows was // moved around, we don't know exactly what was the affected region. Application.Top.SetNeedsDisplay (); } else { SuperView.SetNeedsDisplay (); } EnsureVisibleBounds (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - start.X : Frame.X - start.X), mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - start.Y : Frame.Y - start.Y), out nx, out ny, out _, out _); dragPosition = new Point (nx, ny); X = nx; Y = ny; //System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}"); SetNeedsDisplay (); return true; } } if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) { Application.UngrabMouse (); dragPosition = null; } //System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}"); //System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}"); return false; } /// /// Invoked by as part of /// after the views have been laid out, and before the views are drawn for the first time. /// public virtual void WillPresent () { FocusFirst (); } /// /// Move to the next Mdi child from the . /// public virtual void MoveNext () { Application.MoveNext (); } /// /// Move to the previous Mdi child from the . /// public virtual void MovePrevious () { Application.MovePrevious (); } /// /// Stops and closes this . If this Toplevel is the top-most Toplevel, /// will be called, causing the application to exit. /// public virtual void RequestStop () { if (IsMdiContainer && Running && (Application.Current == this || Application.Current?.Modal == false || Application.Current?.Modal == true && Application.Current?.Running == false)) { foreach (var child in Application.MdiChildes) { var ev = new ToplevelClosingEventArgs (this); if (child.OnClosing (ev)) { return; } child.Running = false; Application.RequestStop (child); } Running = false; Application.RequestStop (this); } else if (IsMdiContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) { var ev = new ToplevelClosingEventArgs (Application.Current); if (OnClosing (ev)) { return; } Application.RequestStop (Application.Current); } else if (!IsMdiContainer && Running && (!Modal || (Modal && Application.Current != this))) { var ev = new ToplevelClosingEventArgs (this); if (OnClosing (ev)) { return; } Running = false; Application.RequestStop (this); } else { Application.RequestStop (Application.Current); } } /// /// Stops and closes the specified by . If is the top-most Toplevel, /// will be called, causing the application to exit. /// /// The toplevel to request stop. public virtual void RequestStop (Toplevel top) { top.RequestStop (); } /// public override void PositionCursor () { if (!IsMdiContainer) { base.PositionCursor (); if (Focused == null) { EnsureFocus (); if (Focused == null) { Driver.SetCursorVisibility (CursorVisibility.Invisible); } } return; } if (Focused == null) { foreach (var top in Application.MdiChildes) { if (top != this && top.Visible) { top.SetFocus (); return; } } } base.PositionCursor (); if (Focused == null) { Driver.SetCursorVisibility (CursorVisibility.Invisible); } } /// /// Gets the current visible Toplevel Mdi child that matches the arguments pattern. /// /// The type. /// The strings to exclude. /// The matched view. public View GetTopMdiChild (Type type = null, string [] exclude = null) { if (Application.MdiTop == null) { return null; } foreach (var top in Application.MdiChildes) { if (type != null && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false) { return top; } else if ((type != null && top.GetType () != type) || (exclude?.Contains (top.Data.ToString ()) == true)) { continue; } return top; } return null; } /// /// Shows the Mdi child indicated by , setting it as . /// /// The Toplevel. /// true if the toplevel can be shown or false if not. public virtual bool ShowChild (Toplevel top = null) { if (Application.MdiTop != null) { return Application.ShowChild (top == null ? this : top); } return false; } /// public override bool OnEnter (View view) { return MostFocused?.OnEnter (view) ?? base.OnEnter (view); } /// public override bool OnLeave (View view) { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); } } /// /// Implements the for comparing two s /// used by . /// public class ToplevelEqualityComparer : IEqualityComparer { /// Determines whether the specified objects are equal. /// The first object of type to compare. /// The second object of type to compare. /// /// if the specified objects are equal; otherwise, . public bool Equals (Toplevel x, Toplevel y) { if (y == null && x == null) return true; else if (x == null || y == null) return false; else if (x.Id == y.Id) return true; else return false; } /// Returns a hash code for the specified object. /// The for which a hash code is to be returned. /// A hash code for the specified object. /// The type of /// is a reference type and is . public int GetHashCode (Toplevel obj) { if (obj == null) throw new ArgumentNullException (); int hCode = 0; if (int.TryParse (obj.Id.ToString (), out int result)) { hCode = result; } return hCode.GetHashCode (); } } /// /// Implements the to sort the /// from the if needed. /// public sealed class ToplevelComparer : IComparer { /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. /// The first object to compare. /// The second object to compare. /// A signed integer that indicates the relative values of and , as shown in the following table.Value Meaning Less than zero /// is less than .Zero /// equals .Greater than zero /// is greater than . public int Compare (Toplevel x, Toplevel y) { if (ReferenceEquals (x, y)) return 0; else if (x == null) return -1; else if (y == null) return 1; else return string.Compare (x.Id.ToString (), y.Id.ToString ()); } } /// /// implementation for the event. /// public class ToplevelClosingEventArgs : EventArgs { /// /// The toplevel requesting stop. /// public View RequestingTop { get; } /// /// Provides an event cancellation option. /// public bool Cancel { get; set; } /// /// Initializes the event arguments with the requesting toplevel. /// /// The . public ToplevelClosingEventArgs (Toplevel requestingTop) { RequestingTop = requestingTop; } } }