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 helpers <WIP> #494

Draft
wants to merge 5 commits into
base: master
Choose a base branch
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

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.

1 participant