Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleans up/Refactors View.Subviews #3962

Merged
merged 26 commits into from
Mar 8, 2025
Merged

Conversation

tig
Copy link
Collaborator

@tig tig commented Mar 6, 2025

There is a lot of cruft in how Subviews (Add/Remove etc...) work. In order to work on these, I need this cleaned up:

Proposed Changes/Todos

  • Make all core code use InternalSubviews vs. Subviews
  • Reorg unit tests
  • Code and API doc cleanup

Pull Request checklist:

  • I've named my PR in the form of "Fixes #issue. Terse description."
  • My code follows the style guidelines of Terminal.Gui - if you use Visual Studio, hit CTRL-K-D to automatically reformat your files before committing.
  • My code follows the Terminal.Gui library design guidelines
  • I ran dotnet test before commit
  • I have made corresponding changes to the API documentation (using /// style comments)
  • My changes generate no new warnings
  • I have checked my code and corrected any poor grammar or misspellings
  • I conducted basic QA to assure all features are working

@tig
Copy link
Collaborator Author

tig commented Mar 6, 2025

Folks (@BDisp , @tznind , etc...)

It's ALWAYS driven me nuts that View.Subviews has a lowercase V (or that SuperView has an upper case V).

Can I fix this, please?

@BDisp
Copy link
Collaborator

BDisp commented Mar 6, 2025

Folks (@BDisp , @tznind , etc...)

It's ALWAYS driven me nuts that View.Subviews has a lowercase V (or that SuperView has an upper case V).

Can I fix this, please?

I like more SubViews and SuperView.

@tig
Copy link
Collaborator Author

tig commented Mar 6, 2025

Thanks.

Here's another topic I'd like your opinion on:

Currently the events around View.Add/Remove are:

    /// <summary>Event fired when this view is added to another.</summary>
    public event EventHandler<SuperViewChangedEventArgs>? Added;

    /// <summary>Method invoked when a subview is being added to this view.</summary>
    /// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
    public virtual void OnAdded (SuperViewChangedEventArgs e)
    {
        View view = e.SubView;
        view.IsAdded = true;
        view.Added?.Invoke (this, e);
    }

    /// <summary>Event fired when this view is removed from another.</summary>
    public event EventHandler<SuperViewChangedEventArgs>? Removed;

    /// <summary>Method invoked when a subview is being removed from this view.</summary>
    /// <param name="e">Event args describing the subview being removed.</param>
    public virtual void OnRemoved (SuperViewChangedEventArgs e)
    {
        View view = e.SubView;
        view.IsAdded = false;
        view.Removed?.Invoke (this, e);
    }

Note that these are super confusing and do not match standard event patterns.

  • OnAdded is called when a SubView is being added to this
  • Added is raised ON THE SubView when the SubView has been added to this

etc...

There are NO overrides of either virtual method in our codebase.

The uses of the events (Added and Removed) are few. And none of them use the eventargs passed; they only use the events as a signal. Eg. TextField:

    private void TextField_Added (object sender, SuperViewChangedEventArgs e)
    {
        if (Autocomplete.HostControl is null)
        {
            Autocomplete.HostControl = this;
            Autocomplete.PopupInsideContainer = false;
        }
    }

This can and should be simplified.

My proposal:

  • Call OnAddedChanged/Raise IsAddedChanged when this.IsAdded changes. Make it EventArg<bool>.
  • Call OnSubViewAdded/Raise SubViewAdded when this.Add is called. This can continue to use SuperViewChangedEventArgs.

(Same for Remove).

thots?

@BDisp
Copy link
Collaborator

BDisp commented Mar 6, 2025

I think that way you're duplicating the event raising twice because IsAddedChanged is the same as using the SubViewAdded . In my opinion the methods OnXXX should be private void because a derived class would not have any reason to override it and broken the event to be raised.

@BDisp
Copy link
Collaborator

BDisp commented Mar 6, 2025

I would use the private void OnSubViewAdded which raise SubViewAdded.

@tig
Copy link
Collaborator Author

tig commented Mar 6, 2025

I think that way you're duplicating the event raising twice because IsAddedChanged is the same as using the SubViewAdded . In my opinion the methods OnXXX should be private void because a derived class would not have any reason to override it and broken the event to be raised.

Please read https://gui-cs.github.io/Terminal.GuiV2Docs/docs/events.html

A subclass of View may want to know when it's been added to a superview.

With my proposal it can either:

a) override OnIsAddedChanged

Or

b) subscribe to IsAddedChanged

Take a look at the latest code i pushed here and let me know what you think.

@@ -59,7 +59,7 @@ public ComboBox ()
// On resize
SubviewsLaidOut += (sender, a) => ProcessLayout ();

Added += (s, e) =>
IsAddedChanged += (s, e) =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a derived class it could override the OnIsAddedChanged.

@@ -84,7 +84,7 @@ public MenuBar ()
WantMousePositionReports = true;
IsMenuOpen = false;

Added += MenuBar_Added;
IsAddedChanged += MenuBar_IsAddedChanged;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a derived class it could override the OnIsAddedChanged.

Added += TextField_Added;

Removed += TextField_Removed;
IsAddedChanged += TextField_IsAddedChanged;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a derived class it could override the OnIsAddedChanged.

@@ -1899,7 +1899,7 @@ public TextView ()

Initialized += TextView_Initialized!;

Added += TextView_Added!;
IsAddedChanged += TextView_IsAddedChanged!;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a derived class it could override the OnIsAddedChanged.

@BDisp
Copy link
Collaborator

BDisp commented Mar 6, 2025

A subclass of View may want to know when it's been added to a superview.

With my proposal it can either:

a) override OnIsAddedChanged

Or

b) subscribe to IsAddedChanged

Take a look at the latest code i pushed here and let me know what you think.

Understood. So I see that protect virtual methods has precedence to events. Only for to be sure.

@tig
Copy link
Collaborator Author

tig commented Mar 7, 2025

A subclass of View may want to know when it's been added to a superview.
With my proposal it can either:
a) override OnIsAddedChanged
Or
b) subscribe to IsAddedChanged
Take a look at the latest code i pushed here and let me know what you think.

Understood. So I see that protect virtual methods has precedence to events. Only for to be sure.

This is precisely the debate here:

#3955 (comment)

This is the current model that is implemented consistently in all of the places where I've modernize the eventing. "OverrideFirst".

@BDisp
Copy link
Collaborator

BDisp commented Mar 7, 2025

A unit test that you maybe could add into the SubviewTests.cs:

    [Fact]
    public void IsAdded_IsAddedChanged_SubViewAdded_SubViewRemoved ()
    {
        var isAdded = false;

        View superView = new () { Id = "superView" };
        View subView = new () { Id = "subView" };

        superView.SubViewAdded += (s, e) =>
                                  {
                                      Assert.True (isAdded);
                                      Assert.True (subView.IsAdded);
                                      Assert.Equal (subView, e.SubView);
                                      Assert.Equal (superView, e.SuperView);
                                  };

        superView.SubViewRemoved += (s, e) =>
                                    {
                                        Assert.False (isAdded);
                                        Assert.False (subView.IsAdded);
                                        Assert.Equal (subView, e.SubView);
                                        Assert.Equal (superView, e.SuperView);
                                    };

        subView.IsAddedChanged += (s, e) => { isAdded = e.CurrentValue; };

        superView.Add (subView);
        Assert.True (isAdded);
        Assert.True (subView.IsAdded);
        Assert.Single (superView.Subviews);

        superView.Remove (subView);
        Assert.False (isAdded);
        Assert.False (subView.IsAdded);
        Assert.Empty (superView.Subviews);
    }

@tig tig marked this pull request as ready for review March 7, 2025 17:54
@tig tig requested review from BDisp and tznind March 7, 2025 17:54
@tig
Copy link
Collaborator Author

tig commented Mar 7, 2025

@tznind it's important you review this and are ok with it. Esp:

  • Changing the event model for View.Add/Remove
  • Renaming Subview to SubView

Sorry so many other files got touched. I got motivated to clean up a bunch of warnings in unit tests and the Subview rename too.

Thanks

@tig tig changed the title Clean's up/Refactors View.Subviews Cleans up/Refactors View.Subviews Mar 8, 2025
@tig
Copy link
Collaborator Author

tig commented Mar 8, 2025

Ok, look again.

  • Nuked View.IsAdded.
  • Added View.SuperViewChanged.

@tig tig merged commit acb5979 into gui-cs:v2_develop Mar 8, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants