Files Community Documentation

repository·main·Indexed 13 days ago

https://github.com/files-community/files

Documentation for the Files community project, including guides for migrating manual P/Invoke and Vanara interop to source-generated CsWin32, and technical references for ActionCommand, HotKey, and HotKeyCollection implementations.

Tokens
10.4K
Snippets
25
Records
35
Agent score
98%

What's inside Files

  1. Workflow for converting interop definitions

    main

    Follow these steps to migrate interop code to CsWin32:

    1. Locate manual interop: Search for DllImport, MarshalAs, StringBuilder, Win32PInvoke, or Vanara usage.
    2. Update NativeMethods.txt: Add the function and all dependent structs, enums, or COM interfaces to src/Files.App.CsWin32/NativeMethods.txt.
    3. Regenerate signatures: Build the CsWin32 project to refresh the generated code.
    4. Update callers: Use generated APIs directly. Prefer safe overloads like Span<char>, SafeHandle, ComPtr<T>, and generated enums/structs. Use unsafe raw overloads only when necessary (e.g., for specific pointer requirements).
    5. Cleanup: Remove the manual definitions only after all callers have been updated.
    6. Verify: Build the project and ensure no error CS* (C# compiler) errors exist.
    # 1. Locate manual interop
    git grep -n "DllImport\|MarshalAs\|StringBuilder" -- src
    git grep -n "Win32PInvoke\." -- src/Files.App
    git grep -n "using Vanara\|Vanara\.PInvoke\|Kernel32\.|\|Shell32\.|\|User32\." -- src/Files.App
    
    # 2. Add to NativeMethods.txt (Example entries)
    # RmStartSession
    # RM_PROCESS_INFO
    # SHBrowseForFolder
    
    # 3. Build CsWin32 project to refresh signatures
    dotnet build src/Files.App.CsWin32/Files.App.CsWin32.csproj -c Debug -p:Platform=x64
    
    # 5. Verify callers are gone before deleting manual definitions
    git grep -n "Win32PInvoke\.RmStartSession" -- src/Files.App
  2. Convert marshaled interop to CsWin32 unmarshaled interop

    main

    The project is migrating from manual, trim-unsafe interop (using DllImport, ComImport, or the Vanara package) to source-generated CsWin32 interop. The goal is to use Windows.Win32.PInvoke and generated types instead of manual declarations.

    Target Shape

    1. Add the native API, COM interface, enum, or struct name to src/Files.App.CsWin32/NativeMethods.txt.
    2. Use Windows.Win32.PInvoke and generated CsWin32 types at the call site.
    3. Delete manual declarations and Vanara references.
    4. Keep Win32PInvoke only for definitions that cannot be generated by CsWin32.

    Conversion Heuristics

    • Buffers: Replace StringBuilder with Span<char> or fixed char* buffers.
    • Handles: Replace IntPtr with SafeFileHandle, SafeHandle, HANDLE, or generated handle structs.
    • Vanara: Treat Vanara removal the same as manual P/Invoke removal. Add the API to NativeMethods.txt and update the call site to use generated types.
    • Convenience: Use generated safe overloads (e.g., LoadLibrary returning a disposable FreeLibrarySafeHandle) to simplify lifetime management.
    // Before (Vanara/Manual)
    var lib = Kernel32.LoadLibrary(file);
    StringBuilder result = new(2048);
    _ = User32.LoadString(lib, number, result, result.Capacity);
    Kernel32.FreeLibrary(lib);
    return result.ToString();
    
    // After (CsWin32)
    using var lib = PInvoke.LoadLibrary(file);
    Span<char> result = stackalloc char[2048];
    int length = PInvoke.LoadString(lib, (uint)number, result, result.Length);
    return result[..length].ToString();
  3. Use ModifiableCommand to implement modifier-key dependent actions

    main

    The ModifiableCommand class implements IRichCommand and allows a single command to behave differently based on the current keyboard modifiers (e.g., Ctrl, Shift, Alt).

    When ExecuteAsync is called, the command checks the current key modifiers using HotKeyHelpers.GetCurrentKeyModifiers(). If a command is registered in the ModifiedCommands dictionary for those specific modifiers, that modified command is executed instead of the BaseCommand. If no match is found, the BaseCommand is executed.

    This is useful for creating UI elements that perform a standard action by default but perform an alternative action when a modifier key is held down.

    // Conceptual usage of ModifiableCommand
    // Requires an IRichCommand as the base and a dictionary mapping KeyModifiers to alternative IRichCommands
    
    var baseCommand = new ActionCommand(...);
    var altCommand = new ActionCommand(...);
    
    var modifiable = new ModifiableCommand(
        baseCommand, 
        new Dictionary<KeyModifiers, IRichCommand> 
        {
            { KeyModifiers.Control, altCommand }
        }
    );
    
    // If Ctrl is held, altCommand.ExecuteAsync() is called.
    // If Ctrl is NOT held, baseCommand.ExecuteAsync() is called.
    await modifiable.ExecuteAsync();
  4. How CommandManager handles key bindings

    main

    The CommandManager manages the relationship between commands and their keyboard shortcuts (HotKey). It performs the following logic:

    1. Customization: It monitors IActionsSettingsService for changes. When settings change, it overwrites default key bindings with user-defined ones from ActionsV2.
    2. Empty Bindings: If a user explicitly sets a command's key binding to an empty string, the manager will remove the default binding for that command.
    3. Conflict Resolution: If duplicate key bindings are detected (e.g., two commands assigned to the same shortcut), the manager attempts to resolve the conflict by restoring default key bindings for the affected commands and cleaning up the user settings to prevent repeated errors.
    4. Fallback: If a serious error occurs during the assignment of custom keys, the manager falls back to using the default key bindings for all commands to ensure stability.
  5. Verify interop conversion via build checks

    main

    After converting interop code, verify the changes using the following checklist:

    • NativeMethods.txt contains every newly used API/type.
    • No caller remains for the deleted manual method or struct.
    • The generated CsWin32 types are used at call sites.
    • The CsWin32 project builds successfully: dotnet build src/Files.App.CsWin32/Files.App.CsWin32.csproj -c Debug -p:Platform=x64
    • The main app project has no new C# compiler errors (error CS*).

    Note: If you encounter MSB3073 errors, these are WinUI XAML compiler failures and may be unrelated to your interop changes. Distinguish these from error CS* failures when reporting issues.

    dotnet build src/Files.App.CsWin32/Files.App.CsWin32.csproj -c Debug -p:Platform=x64
    dotnet build src/Files.App/Files.App.csproj -c Debug -p:Platform=x64
  6. New Analyzer Rules in Files.Core.SourceGenerator

    main

    The Files.Core.SourceGenerator includes several new analyzer rules for design, refactoring, and file generation. These rules are categorized by their impact on the codebase and their severity level (Error or Warning).

    Rule IDCategorySeverityNotes
    FSG1001DesignErrorFSG1001_Files.Core.SourceGenerator
    FSG1002RefactoringWarningFSG1002_Files.Core.SourceGenerator
    FSG1003FileGenerationErrorFSG1003_Files.Core.SourceGenerator
    | Rule ID | Category | Severity | Notes |
    |--------|----------|----------|--------------------|
    | FSG1001 |  Design  |   Error  | FSG1001_Files.Core.SourceGenerator |
    | FSG1002 | Refactoring | Warning | FSG1002_Files.Core.SourceGenerator |
    | FSG1003 | FileGeneration | Error | FSG1003_Files.Core.SourceGenerator |
  7. Access modifiable commands via ModifiableCommandManager

    main

    The ModifiableCommandManager provides access to commands that support alternative behaviors based on key modifiers (e.g., using the Shift key). You can access these commands using the indexer with a CommandCodes key or through explicit properties for common commands. If a requested command code is not found, the manager returns a None command.

    // Accessing via explicit properties
    IRichCommand paste = modifiableCommandManager.PasteItem;
    
    // Accessing via indexer with CommandCodes
    IRichCommand delete = modifiableCommandManager[CommandCodes.DeleteItem];
    
    // Accessing the fallback 'None' command
    IRichCommand none = modifiableCommandManager.None;
  8. Use the HotKey struct for keyboard shortcuts

    main

    The HotKey struct represents a keyboard shortcut consisting of a Keys value and KeyModifiers. It provides methods for creating, parsing, and displaying hotkeys in both raw and localized formats.

    Key Properties

    • Key: The specific Keys value (e.g., Keys.A, Keys.Enter).
    • Modifier: The KeyModifiers applied (e.g., Alt, Ctrl, Shift, Win).
    • IsVisible: A boolean indicating if the hotkey should be available/visible in the UI.
    • RawLabel: A string representation of the hotkey (e.g., "Ctrl+A").
    • LocalizedLabel: A string representation using localized names for keys and modifiers (e.g., "Control+A").
    • IsNone: Returns true if both the key and modifier are None.
    // Creating a hotkey manually
    var myHotKey = new HotKey(Keys.A, KeyModifiers.Control);
    
    // Checking if it is a 'None' hotkey
    if (myHotKey.IsNone) { /* ... */ }
    
    // Getting the localized string for UI display
    string label = myHotKey.LocalizedLabel;
  9. Access commands via IModifiableCommandManager

    main

    The IModifiableCommandManager interface provides access to a collection of IRichCommand objects. It allows for retrieving specific commands using a CommandCodes enumeration indexer or through predefined properties for common operations like pasting, deleting, or opening properties.

    // Accessing a command via the indexer using CommandCodes
    IRichCommand myCommand = commandManager[CommandCodes.SomeCode];
    
    // Accessing common commands via properties
    IRichCommand pasteCommand = commandManager.PasteItem;
    IRichCommand deleteCommand = commandManager.DeleteItem;
    IRichCommand propertiesCommand = commandManager.OpenProperties;
    
    // Accessing the null/none command
    IRichCommand noneCommand = commandManager.None;
  10. Parse humanized hotkey strings

    main

    The HotKey.Parse method converts a human-readable string into a HotKey instance. It supports both localized and non-localized strings.

    • Localized parsing (default): Uses LocalizedKeys and LocalizedModifiers to match string parts to enum values. This is useful for parsing strings displayed in the UI.
    • Non-localized parsing: Uses Enum.TryParse on the string parts to match Keys and KeyModifiers directly.

    Supported separator: +.

    Parsing Examples

    // Parsing a localized string (default behavior)
    var hotkey = HotKey.Parse("Control+Shift+A");
    
    // Parsing a non-localized string (using enum names)
    var hotkeyRaw = HotKey.Parse("Control+Shift+A", localized: false);
    
    // Parsing a single key
    var singleKey = HotKey.Parse("Enter");
    // Example of parsing a localized string
    HotKey hk = HotKey.Parse("Control+Alt+Delete");
  11. Access commands via CommandManager

    main

    The CommandManager provides a centralized way to retrieve and execute commands within the application. You can access specific commands using several indexing methods:

    • By CommandCodes enum: Use the strongly-typed enum value.
    • By string code: Use a string representation of the command code (case-insensitive). If the string does not match a valid code, it returns None.
    • By HotKey: Retrieve a command associated with a specific keyboard shortcut. The manager checks both visible and non-visible key bindings.

    Commands are grouped via the Groups property, which allows for organized command discovery.

    // Access by CommandCodes enum
    IRichCommand command = commandManager[CommandCodes.SomeCommand];
    
    // Access by string code
    IRichCommand command = commandManager["SomeCommand"];
    
    // Access by HotKey
    IRichCommand command = commandManager[new HotKey(...)];
    
    // Access command groups
    var groups = commandManager.Groups;
  12. Convert RichGlyph to UI elements

    main

    A RichGlyph can be converted into various WinUI XAML elements for display in the application UI:

    • ToIcon(): The primary method for obtaining a visual representation. It attempts to return a ThemedIcon first; if no themed style is provided, it falls back to a FontIcon.
    • ToFontIcon(): Returns a FontIcon using the BaseGlyph and FontFamily. Returns null if IsNone is true.
    • ToThemedIcon(): Returns a ThemedIcon using the ThemedIconStyle resource. Returns null if no style is provided.
    • ToThemedIconStyle(): Returns the Style object retrieved from Application.Current.Resources using the ThemedIconStyle key.
    • ToOverflowIcon(): Returns a PathIcon by extracting path data (Outline or Filled) from the ThemedIconStyle. This is useful for overflow menus where a simple path is required.
    RichGlyph myGlyph = new RichGlyph(, "Segoe Fluent Icons");
    
    // Get a generic icon object for UI binding
    object? icon = myGlyph.ToIcon();
    
    // Specifically get a FontIcon
    FontIcon? fontIcon = myGlyph.ToFontIcon();
    
    // Specifically get a PathIcon for overflow menus
    IconElement? pathIcon = myGlyph.ToOverflowIcon();