dahall / Vanara

A set of .NET libraries for Windows implementing PInvoke calls to many native Windows APIs with supporting wrappers.
MIT License
1.81k stars 196 forks source link

How to use native Win32 Shell ImageList in WinUI3 - Implementing ExplorerBrowser for WinUI3 #444

Closed tajbender closed 1 month ago

tajbender commented 8 months ago

Is your feature request related to a problem? Please describe

I'm using WinAppSDK for some months now. I wonder how to use a native System Win32 API ImageList in UWP / WinUI? No changes to Vanara needed, i guess.

Any Idea, David? Thx.

Describe the additions or enhancements you'd like

// convert system wide shellItemList into xaml based type var winUIxamlObject = Vanara.*.SysImageList as || .SomeConversion()

dahall commented 8 months ago

@tajbender I haven't done much with WinUI. There is a package called Vanara.WinUI.Extensions where we can land anything you want to add. Right now, that assembly is pretty light. I'll accept any PR you post.

tajbender commented 8 months ago

@dahall Thank you very much. I really appreciate this.

I thought about aligning the interfaces of the WinUI3 ExplorerBrowser aka ShellListView / ShellTreeView that I'll implement the next days to those of Vanaras Win32 Ones.

That way Migrating that stuff from there to the WinUI-Version within Vanara would just be a piece of cake.

I'll let you know as soon there's something to talk about.

Have a nice Easter!

dahall commented 8 months ago

Cool! Thanks. Happy Easter to you too!

tajbender commented 8 months ago

@tajbender I haven't done much with WinUI.

btw, they have done an excellent job with WinUI3, in my humble opinion.

I've never worked with WPF and UWP before, but WinUI3 is straightforward as soon you get the point. They generate code-behind for the UI while compiling, this way, the UI-stuff is amazingly fast.

They also use DirectX / DirectDraw for rendering stuff. On the other side, you can use WinRT for the abstraction layer to OS for DOS, manipulating HWND settings / Messaging and so on: ๐Ÿ”

๐Ÿ‘

tajbender commented 8 months ago

Done so far.

tajbender commented 8 months ago

addendum: These classes my help out:

https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/win32/microsoft.ui.interop/

tajbender commented 7 months ago

addendum: This should do it, for single images:

    Bitmap bitmap1 = Bitmap.FromHicon(SystemIcons.Hand.Handle);
    Graphics formGraphics = this.CreateGraphics();
    GraphicsUnit units = GraphicsUnit.Point;

    RectangleF bmpRectangleF = bitmap1.GetBounds(ref units);
    Rectangle bmpRectangle = Rectangle.Round(bmpRectangleF);
    formGraphics.DrawRectangle(Pens.Blue, bmpRectangle);
    formGraphics.Dispose();

https://learn.microsoft.com/en-us/dotnet/api/system.drawing.systemicons?view=windowsdesktop-3.1

dahall commented 6 months ago

@tajbender What type(s) do you want converted from and to? I don't have a SysImageList class. Do you mean a native image list handle (HIMAGELIST)? What is the output type under WinUI3?

tajbender commented 6 months ago

@tajbender What type(s) do you want converted from and to? I don't have a SysImageList class. Do you mean a native image list handle (HIMAGELIST)? What is the output type under WinUI3?

@dahall Thanks for Reply, but you don't have to care about the Notes above. I just wrote down what I've found so far, so they're just a reminder for myself ๐Ÿงท

You're right, with SysImageList I refer to the Win32 - HIMAGELIST handle, you could get with SHGFI_SYSICONINDEX flag, which returns the Index to the Shell Icon you're looking for.

Using Win32 you can pass this Handle to your ListView or TreeView controls directly, to avoid copying the images around from System space to your own Heap.

Anyway, you don't have to care about these comments, these are reminders for myself.

Sorry for confusion and bothering, anyways Thank you very much, regards, tajbender

dahall commented 6 months ago

I think you may be able to use Vanara.Windows.Shell.ShellImageList (in Vanara.Windows.Shell.Common) to expose the SafeHICON of system files or folders and wrap that into a class the derives from IconSource to provide the converted icon handle to a bitmap.

tajbender commented 6 months ago

I think you may be able to use Vanara.Windows.Shell.ShellImageList (in Vanara.Windows.Shell.Common) to expose the SafeHICON of system files or folders and wrap that into a class the derives from IconSource to provide the converted icon handle to a bitmap.

public static ImageSource ToImageSource(this Icon icon)
{
    ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon(
        icon.Handle,
        Int32Rect.Empty,
        BitmapSizeOptions.FromEmptyOptions());
    return imageSource;
}

That's what Copilot says ๐Ÿ‘Œ

I'll try this asap. Thank you very much ๐Ÿ‘

tajbender commented 5 months ago

@dahall: I uploaded an YouTube-Video

That's the current state of the WinUI3 Shell - Components I'm working on, using Vanara.

Screenshot 2024-06-04 101413

You may look at the current branch here.

I still may need your help on some occasions, but I'm satisfied for now ๐Ÿ˜€

Just for your Info, regards, tajbender

dahall commented 5 months ago

I just committed a class I'll use for this: Vanara.Windows.Shell.ShellIconExtractor

tajbender commented 5 months ago

I just committed a class I'll use for this: Vanara.Windows.Shell.ShellIconExtractor

Thank you very much. This will help a lot. ๐Ÿฅ‡

tajbender commented 4 months ago

Dear @dahall

Love 4 support ๐Ÿง‘โ€โš•๏ธ

Currently, โšฝ European Championships take place in Germany, so no time no money ๐Ÿฐ

I used the new code already, it works stable ๐Ÿฅ‡

The next days, I will create a Pull Request for merging the stuff I have so far to Vanara. It still is WIP. But some review would help a lot.

Regards,

tajbender

tajbender commented 4 months ago

I just committed a class I'll use for this: Vanara.Windows.Shell.ShellIconExtractor

Hi, @dahall

I used the ShellIconExtractor for some days now.

I'm new to that multithreading stuff, but anyway I get some strange behavior: Enumerating the same Folder again and again when navigating through the pages and views, ShellIconExtractor in my App may result in different findings:

08:40:10:034    'electrifier.exe' (CoreCLR: DefaultDomain): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.19\System.Private.CoreLib.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:10:389    'electrifier.exe' (CoreCLR: clrhost): Loaded 'Snippets'. 
08:40:10:892    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\runtimes\win\lib\net7.0\System.Diagnostics.EventLog.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:10:892    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\Vanara.Windows.Shell.Common.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:10:892    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\Vanara.PInvoke.Ole.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:10:892    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\Vanara.PInvoke.Shell32.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:10:892    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\Vanara.PInvoke.Shared.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:11:883    Error: Converter failed to convert value of type 'Windows.Foundation.IReference`1<Microsoft.UI.Xaml.GridLength>' to type 'Double'; BindingExpression: Path='TemplateSettings.CompactPaneGridLength' DataItem='Microsoft.UI.Xaml.Controls.SplitView'; target element is 'Microsoft.UI.Xaml.Media.Animation.SplineDoubleKeyFrame' (Name='null'); target property is 'Value' (type 'Double'). 
08:40:14:383    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\Vanara.Core.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:14:631    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\Vanara.PInvoke.Gdi32.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:14:631    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\Vanara.Windows.Extensions.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:14:631    'electrifier.exe' (CoreCLR: clrhost): Loaded 'D:\gitHub\tajbender\electrifier.v1.24\src\electrifier\bin\x64\Debug\net7.0-windows10.0.19041.0\AppX\Vanara.PInvoke.User32.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:15:144    .IconExtOnComplete(): 63 items
08:40:15:144    .SetItemsSource(): `Desktop` 63 items.
08:40:16:630    .IconExtOnComplete(): 61 items
08:40:16:630    .SetItemsSource(): `Desktop` 61 items.
08:40:16:630    .GridViewVisibility = Visible
08:40:23:882    'electrifier.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.19\System.Reflection.Emit.Lightweight.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:23:882    'electrifier.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.19\System.Reflection.Emit.ILGeneration.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:23:882    'electrifier.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.19\System.Reflection.Primitives.dll'. Symbol loading disabled by Include/Exclude setting.
08:40:24:130    .IconExtOnComplete(): 64 items
08:40:24:130    .SetItemsSource(): `Desktop` 64 items.
08:40:27:631    .IconExtOnComplete(): 64 items
08:40:27:631    .SetItemsSource(): `Desktop` 64 items.
08:40:27:631    .GridViewVisibility = Visible
08:40:47:395    The thread '[Thread Destroyed]' (12448) has exited with code 0 (0x0).
08:40:47:645    The thread '[Thread Destroyed]' (27936) has exited with code 0 (0x0).
08:40:50:430    .IconExtOnComplete(): 63 items
08:40:50:430    .SetItemsSource(): `Desktop` 63 items.
08:40:52:736    .IconExtOnComplete(): 64 items
08:40:52:736    .SetItemsSource(): `Desktop` 64 items.
08:40:52:984    .GridViewVisibility = Visible
08:40:55:740    .IconExtOnComplete(): 64 items
08:40:55:740    .SetItemsSource(): `Desktop` 64 items.
08:40:57:780    .IconExtOnComplete(): 64 items
08:40:57:780    .SetItemsSource(): `Desktop` 64 items.
08:40:57:780    .GridViewVisibility = Visible
08:41:00:578    .IconExtOnComplete(): 64 items
08:41:00:578    .SetItemsSource(): `Desktop` 64 items.
08:41:03:657    .IconExtOnComplete(): 64 items
08:41:03:657    .SetItemsSource(): `Desktop` 64 items.
08:41:03:657    .GridViewVisibility = Visible
08:41:05:689    .IconExtOnComplete(): 62 items
08:41:05:689    .SetItemsSource(): `Desktop` 62 items.
08:41:25:456    The thread '[Thread Destroyed]' (8484) has exited with code 0 (0x0).
08:41:25:697    The thread '[Thread Destroyed]' (22372) has exited with code 0 (0x0).

Everytime .IconExtOnComplete(): has finished, my desktop folder has been enumerated. As you may see, I may get different number of items while doing so (62, 63, 64). ๐Ÿ˜’

In Fact, it should be 63 items... I'll investigate further ๐Ÿค” ๐Ÿ’ญ

Have a nice, sunny day ๐ŸŒž

tajbender commented 4 months ago

Hi, @dahall

I used the ShellIconExtractor for some days now.

I'm new to that multithreading stuff, but anyway I get some strange behavior: Enumerating the same Folder again and again when navigating through the pages and views, ShellIconExtractor in my App may result in different findings:

Everytime .IconExtOnComplete(): has finished, my desktop folder has been enumerated. As you may see, I may get different number of items while doing so (62, 63, 64). ๐Ÿ˜’

In Fact, it should be 63 items... I'll investigate further ๐Ÿค” ๐Ÿ’ญ

Have a nice, sunny day ๐ŸŒž

@dahall: Never mind, after some changes/optimization, I can't reproduce this behavior.

It should have been caused by limited Multi-Threading Knowledge on my side ๐Ÿ˜€

Regards, tajbender

tajbender commented 3 months ago

๐Ÿ“ฅ For further Development, I've created my own branch for Additions:

https://github.com/electrifier/Vanara-Additions/tree/WinUI3-Shell32-Controls

tajbender commented 2 months ago

For converting a HBitmap, you can use:

    public ImageEx? ImageIconSource
    {
        get;
        internal set;
    }

Then use https://learn.microsoft.com/en-us/windows/communitytoolkit/controls/imageex:

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

// TODO: For EnumerateChildren-Calls, add HWND handle
// TODO: See ShellItemCollection, perhaps use this instead of ObservableCollection
// https://github.com/dahall/Vanara/blob/master/Windows.Shell.Common/ShellObjects/ShellItemArray.cs

namespace electrifier.Controls.Vanara;

public sealed partial class Shell32TreeView : UserControl
{
    public TreeView NativeTreeView => TreeView;

    public object ItemsSource
    {
        get => NativeTreeView.ItemsSource;
        set => NativeTreeView.ItemsSource = value;
    }

    public ExplorerBrowserItem? SelectedItem
    {
        get => (ExplorerBrowserItem?)GetValue(SelectedItemProperty);
        set => SetValue(SelectedItemProperty, value);
    }
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register(nameof(SelectedItem), typeof(ExplorerBrowserItem), typeof(Shell32TreeView),
            new PropertyMetadata(default(ExplorerBrowserItem?)));

    public TreeViewNode SelectedNode => NativeTreeView.SelectedNode;

    public Shell32TreeView()
    {
        InitializeComponent();
        DataContext = this;
    }

    // TODO: public object ItemFromContainer => NativeTreeView.ItemFromContainer()
}
tajbender commented 1 month ago

@tajbender I haven't done much with WinUI. There is a package called Vanara.WinUI.Extensions where we can land anything you want to add. Right now, that assembly is pretty light. I'll accept any PR you post.

Hello, @dahall

I'd like to add some helpers and Controls in beta state now, just for convenience. However, some more work is necessary to polish them.

Regards, tajbender

tajbender commented 1 month ago

BTW: This snippet is working finally:

    /// <summary>
    /// Taken from <see href="https://stackoverflow.com/questions/76640972/convert-system-drawing-icon-to-microsoft-ui-xaml-imagesource"/>
    /// </summary>
    /// <param name="bitmapIcon"></param>
    /// <returns></returns>
    public static async Task<SoftwareBitmapSource> GetWinUi3BitmapSourceFromIcon(Icon bitmapIcon)
    {
        if (bitmapIcon == null)
            return null;

        // convert to bitmap
        using var bmp = bitmapIcon.ToBitmap();
        return await GetWinUi3BitmapSourceFromGdiBitmap(bmp);
    }

    /// <summary>
    /// Taken from <see href="https://stackoverflow.com/questions/76640972/convert-system-drawing-icon-to-microsoft-ui-xaml-imagesource"/>
    /// See also <see href="https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.image.source?view=windows-app-sdk-1.5#microsoft-ui-xaml-controls-image-source"/>
    /// </summary>
    /// <param name="gdiBitmap"></param>
    /// <returns></returns>
    public static async Task<SoftwareBitmapSource> GetWinUi3BitmapSourceFromGdiBitmap(Bitmap gdiBitmap)
    {
        if (gdiBitmap == null)
            return null;

        // get pixels as an array of bytes
        var data = gdiBitmap.LockBits(new System.Drawing.Rectangle(0, 0, 
                                      gdiBitmap.Width, gdiBitmap.Height), 
                   System.Drawing.Imaging.ImageLockMode.ReadOnly, 
                   gdiBitmap.PixelFormat);
        var bytes = new byte[data.Stride * data.Height];
        Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
        gdiBitmap.UnlockBits(data);

        // get WinRT SoftwareBitmap
        var softwareBitmap = new Windows.Graphics.Imaging.SoftwareBitmap(
            Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8,
            gdiBitmap.Width,
            gdiBitmap.Height,
            Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied);
        softwareBitmap.CopyFromBuffer(bytes.AsBuffer());

        // build WinUI3 SoftwareBitmapSource
        var source = new SoftwareBitmapSource();
        await source.SetBitmapAsync(softwareBitmap);
        return source;
    }