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

Initial commit of WinUI3 Shell32 Controls, Services, Helper <WIP> #494

Closed
wants to merge 6 commits into from

Conversation

tajbender
Copy link
Contributor

Initial commit of WinUI3 Shell32 helpers.

For Reference only.

Copy link
Contributor Author

@tajbender tajbender left a comment

Choose a reason for hiding this comment

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

This is a stub actually.

More coming soon.

@tajbender
Copy link
Contributor Author

tajbender commented Oct 25, 2024

Hello, @dahall

I'm working on the WinUI3 stuff using the brand new IconExtractor again.

When enumerating some special folders, the IconExtractor throws errors when then trying to get the DisplayName:

Code involved at public void ExtractChildItems( ... ):

        Debug.Assert(targetFolder.IsFolder);
        Debug.Assert(targetFolder.ShellItem.PIDL != null);
        var shItemId = targetFolder.ShellItem.PIDL;
        var shFolder = new ShellFolder(shItemId);
        var shellIconExtractor = new ShellIconExtractor(shFolder);
        shellIconExtractor.IconExtracted += (sender, args) =>
        {
            var shItem = new ShellItem(args.ItemID);
            var ebItem = new ExplorerBrowserItem(shItem);

            DispatcherQueue.TryEnqueue(() =>
            {
                CurrentFolderItems.Add(ebItem);
            });
        };
        shellIconExtractor.IconExtracted += iconExtOnIconExtracted;
        shellIconExtractor.Complete += iconExtOnComplete;
        shellIconExtractor.Start();

...

    public ExplorerBrowserItem(ShellItem? shItem, bool isSeparator = false)
    {
        ShellItem = new ShellItem(shItem.PIDL);
        DisplayName = ShellItem.Name ?? ":error: <DisplayName.get()>";
        IsExpanded = false;
        // todo: If IsSelected, add overlay of opened folder icon to TreeView optionally
        IsSelected = false;
    }

The folders that don't work are virtual ones: Home, This PC, Network.

You can (hopefully) build the branch involved from this:

You can see the result here:

Any ideas? Didn't debug into your code, cause I'm not privileged enough to understand it 🤣

Keep in mind, that there are no icons involved, no navigation is working ⚒

Any thoughts on this?

Thank you very much,
regards,
tajbender

@tajbender tajbender marked this pull request as draft October 25, 2024 08:32
@tajbender
Copy link
Contributor Author

You don't have to care. I'm bringing in Unit-Tests for this Issue, to track it down...

My 1st Unit Testing 👅 🥇

@tajbender tajbender closed this Nov 2, 2024
@tajbender tajbender reopened this Nov 15, 2024
@tajbender
Copy link
Contributor Author

tajbender commented Nov 15, 2024

Hello, @dahall

I've made some progress with the Shell controls for WinUI the last weeks.

I will update this pull request soon, I guess. In the meantime, a made a little class diagram from what I've got so far:

image

As one can see, It's the same class hierarchy native ExplorerBrowser is using.

There is one difference though: I'm using abstract classes for what represents a single shell Item.

namespace electrifier.Controls.Vanara.Contracts;

[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
public abstract class AbstractBrowserItem<T>(bool isFolder, List<AbstractBrowserItem<T>>? childItems)
{
    public readonly bool IsFolder = isFolder;       // WARN: TODO: Check this. If unknown, then find it out!  ... edit: or use virtual function for this!
    public readonly List<AbstractBrowserItem<T>> ChildItems = childItems ?? [];
    public SoftwareBitmapSource SoftwareBitmapSource = isFolder
        ? IShellNamespaceService.FolderBitmapSource
        : IShellNamespaceService.DocumentBitmapSource;
    public new string ToString() => $"AbstractBrowserItem(<{typeof(T)}>(isFolder {isFolder}, childItems {childItems})";
}

[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
public abstract class AbstractBrowserItemCollection<T> : IEnumerable<AbstractBrowserItem<T>>, IList<AbstractBrowserItem<T>>
{
    //protected readonly ShellItem? _parentOwnerItem;
    protected readonly IList<AbstractBrowserItem<T>> Collection = [];

    AbstractBrowserItem<T> IList<AbstractBrowserItem<T>>.this[int index] { get => Collection[index]; set => Collection[index] = value; }
    int ICollection<AbstractBrowserItem<T>>.Count => Collection.Count;
    bool ICollection<AbstractBrowserItem<T>>.IsReadOnly => false;
    void ICollection<AbstractBrowserItem<T>>.Add(AbstractBrowserItem<T> item) => Collection.Add(item);
    void ICollection<AbstractBrowserItem<T>>.Clear() => Collection.Clear();
    bool ICollection<AbstractBrowserItem<T>>.Contains(AbstractBrowserItem<T> item) => Collection.Contains(item);
    void ICollection<AbstractBrowserItem<T>>.CopyTo(AbstractBrowserItem<T>[] array, int arrayIndex) => Collection.CopyTo(array, arrayIndex);
    IEnumerator<AbstractBrowserItem<T>> IEnumerable<AbstractBrowserItem<T>>.GetEnumerator() => Collection.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator();
    int IList<AbstractBrowserItem<T>>.IndexOf(AbstractBrowserItem<T> item) => Collection.IndexOf(item);
    void IList<AbstractBrowserItem<T>>.Insert(int index, AbstractBrowserItem<T> item) => Collection.Insert(index, item);
    bool ICollection<AbstractBrowserItem<T>>.Remove(AbstractBrowserItem<T> item) => Collection.Remove(item);
    void IList<AbstractBrowserItem<T>>.RemoveAt(int index) => Collection.RemoveAt(index);

    public new string ToString() => $"AbstractBrowserItemCollection(<{typeof(T)}>(number of child items: {Collection.Count})";
}

This actually leads to the following (wip) descent BrowserItem of AbstractBrowserItem<ShellItem>:

public class BrowserItem(Shell32.PIDL pidl, bool isFolder, List<AbstractBrowserItem<ShellItem>>? childItems = default)
    : AbstractBrowserItem<ShellItem>(isFolder, childItems), INotifyPropertyChanged
{
    public readonly Shell32.PIDL PIDL = new(pidl);
    public string DisplayName => ShellItem.GetDisplayName(ShellItemDisplayString.NormalDisplay) ?? ShellItem.ToString();
    public ShellItem ShellItem = new(pidl);
    public new ObservableCollection<BrowserItem> ChildItems = [];
    public static BrowserItem FromPIDL(Shell32.PIDL pidl) => new(pidl, false);
    public static BrowserItem FromShellFolder(ShellFolder shellFolder) => new(shellFolder.PIDL, true);
    public static BrowserItem FromKnownFolderId(Shell32.KNOWNFOLDERID knownItemId) => new(new ShellFolder(knownItemId).PIDL, true);
    public Task<int> Enumerate() {}
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { }
    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null) { }
}

As one can see, it's pretty straightforward. However, I'm not sure how massive the impact of using INotifyPropertyChanged will be.

@dahall: But, in my humble opinion, there should be none, what're your ideas on this?

I'm writing things down so no one is wondering why I'm using such an approach. That is, cause i hope to get things abstract enough to have a generic platform for any structured content, like databases etc. pp.

Regards and thanks for readings,

tajbender

@tajbender tajbender changed the title Initial commit of WinUI3 Shell32 helpers <WIP> Initial commit of WinUI3 Shell32 Controls, Services, Helper <WIP> Jan 13, 2025
@tajbender
Copy link
Contributor Author

Moin,

@dahall: an initial Version of ExplorerBrowser for WinUI3 and its related stuff might be ready for public audience the next days.

  • After Fixed #488 that ShellFolder.EnumerateChildIds might throw NullReferenceException if failed to enum the objects #499, there are no more critical issues, at least on my machine.
  • Currently, only StockIcons are used for the Icon Images. These, however, are already properly retrieved by Shell32 APIs and converted for Use in WinUI.
  • Multi Threading is still not implemented properly. Maybe You find the time to do some Review of the code?
  • I've learned you plan to Release a 2025 version, soon. I plan to have a stable version before the end of January, at least, I hope to find the time 👅
  • So, I would like to integrate the WIP so far into the next and second Vanara Release in 2025, if everything integrates fine. Having those in the nightly build would make sense I guess, don't you, @dahall

So, after your next official Release I'd finally try to merge my stuff for an upcoming Release.

@dahall Do you agree? 😁

Regards,
tajbender

Hello, @dahall

I'm working on the WinUI3 stuff using the brand new IconExtractor again.

When enumerating some special folders, the IconExtractor throws errors when then trying to get the DisplayName:

Code involved at public void ExtractChildItems( ... ):

        Debug.Assert(targetFolder.IsFolder);
        Debug.Assert(targetFolder.ShellItem.PIDL != null);
        var shItemId = targetFolder.ShellItem.PIDL;
        var shFolder = new ShellFolder(shItemId);
        var shellIconExtractor = new ShellIconExtractor(shFolder);
        shellIconExtractor.IconExtracted += (sender, args) =>
        {
            var shItem = new ShellItem(args.ItemID);
            var ebItem = new ExplorerBrowserItem(shItem);

            DispatcherQueue.TryEnqueue(() =>
            {
                CurrentFolderItems.Add(ebItem);
            });
        };
        shellIconExtractor.IconExtracted += iconExtOnIconExtracted;
        shellIconExtractor.Complete += iconExtOnComplete;
        shellIconExtractor.Start();

...

    public ExplorerBrowserItem(ShellItem? shItem, bool isSeparator = false)
    {
        ShellItem = new ShellItem(shItem.PIDL);
        DisplayName = ShellItem.Name ?? ":error: <DisplayName.get()>";
        IsExpanded = false;
        // todo: If IsSelected, add overlay of opened folder icon to TreeView optionally
        IsSelected = false;
    }

The folders that don't work are virtual ones: Home, This PC, Network.

@dahall
Copy link
Owner

dahall commented Jan 13, 2025

@tajbender That's awesome! Thanks for all your work on this. Let me know as you get closer so I can plan for the release.

@tajbender
Copy link
Contributor Author

tajbender commented Jan 13, 2025

@tajbender That's awesome! Thanks for all your work on this. Let me know as you get closer so I can plan for the release.

I must thank you, @dahall. Without your work I'd never have managed to get so far 👅

To be honest, the hardest Issue I had was #499, so I must tribute @zhuxb711 👍 - A lot of functionality has been removed in the meantime to track THIS single nasty 😿 beast down.

However, things still on the Bucket List:

  • Navigation Log
  • Icon Extraction for executables, documents etc. pp
  • Context Menus
  • Double Pane View (Horizontal, Vertical), Preview Pane
  • MVVM for binding individual user defined commands (like copy, paste, filter, etc. pp)

To be honest, I don't have a Win 10 Machine lying around, so it's only tested on Windows 11 '24h2. That's why I look for feedback to get it as bullet proof as it can be.

As you may know, the 🍰 is a lie... So, yes, as soon as there is something that fits together (again), I'll let you know. Thank you very much for your endless support.

Have a good time,
Regards,
tajbender

@tajbender
Copy link
Contributor Author

Moin @dahall

I made a short video, to check performance and memory footprint. That's because I've trying to implemented a simple Cache for folder enumeration results. It may be deactivated in the future, but I was wondering how the memory footprint and CPU utilization are impacting the machine.

Look for yourself, but it's not that high. That is, however, what I've been expected, cause only PIDL and Attributes are cached in a simple array for each of the Items:

https://youtu.be/iPXWrHluDmk

Regards,
tajbender

@dahall
Copy link
Owner

dahall commented Jan 15, 2025

It is performing really well. Congratulations.

@tajbender
Copy link
Contributor Author

tajbender commented Jan 15, 2025

It is performing really well. Congratulations.

I hate slow programs 👿 Maybe because of my age, my little remaining lifetime 🧓 - and finally, that's the reason I never used WPF nor UWP.

Let's finally see the difference when extracting icons and thumbnails :octocat:

edit: Oh, another question: Would the requirement of .net 8 for the WinUI3 package be a showstopper for you? Beside the 3rd Party Community Toolkit that is used?

@dahall
Copy link
Owner

dahall commented Jan 15, 2025

Would the requirement of .net 8 for the WinUI3 package be a showstopper for you? Beside the 3rd Party Community Toolkit that is used?

.NET 8 is supported, but so are all versions back to 4.5. We can use compiler conditions though to limit this control to the .NET 8 or greater builds.

@dahall
Copy link
Owner

dahall commented Jan 15, 2025

@tajbender Should I hold up the 4.5.0 release to include this? I'm planning to push it out this weekend. If so, I'll have you delete the changes to the .csproj and the workflows and just submit the changes to IconExtension.cs. I then can make it work within the project based on its dependencies.

@dahall
Copy link
Owner

dahall commented Jan 15, 2025

Also, what is the difference between this and #493? If one is redundant or expired, will you please remove it? Thanks!

@tajbender
Copy link
Contributor Author

Hi, David,

sorry for confusion, I try to clear things up:

@tajbender Should I hold up the 4.5.0 release to include this? I'm planning to push it out this weekend. If so, I'll have you delete the changes to the .csproj and the workflows and just submit the changes to IconExtension.cs. I then can make it work within the project based on its dependencies.

  • No, this makes no sense, cause still Work in Progress.
  • I hope to get things done for the second next Release v4.5.1 - can't promise even that, cause: It's done when it's done 🤣
  • I'll delete these orphans, didn't expect this experiment would take that long, sorry for confusion

.NET 8 is supported, but so are all versions back to 4.5. We can use compiler conditions though to limit this control to the .NET 8 or greater builds.

Thanks. When I was starting using WinUI3, I learned to need at least .net 6 for using Windows App SDK - at least for the beta releases then.
In the meantime, my own test solution used and still uses .net 7, and it works fine currently.

There are warnings, however, I may or even should use .net 8 cause of Issues with the Runtime Wrapper RTW.

These are currently *warnings* as said, some came and disappeared then and when making slight changes.

I didn't dig further as of now, but I guess .net 6 is an official requirement. My short research didn't dig up further details, cause NuGet at least is somehow quiet on this topic: Dependencies: No Dependencies 🍭

However, my conclusion: I'll try to revert the current mess, and restart a clean, somehow tested on Win11 Release of my work, since there have been many changes and rewrites in the last weeks.

For Win 10 I'll test in a VM with the lowest Win10 release i can make it to work with. However, on my bucket list this has low priority currently. No stable version makes tests on lower versions useless at the moment.

If you like or even need to, you my close both Issues / PRs.

Then, I will re-submit my final version, including Unit Tests and docs. I guess that would be the best way to clear things up.

Thanks for patience, and sorry for confusion.

Regards,
tajbender

@tajbender tajbender closed this Jan 16, 2025
@tajbender tajbender deleted the master branch January 16, 2025 04:13
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.

2 participants