gpt4 book ai didi

.net - 在 Powershell 中使用升级的 FolderBrowserDialog ("Vista style")

转载 作者:行者123 更新时间:2023-12-04 12:33:21 35 4
gpt4 key购买 nike

我正在使用 PowerShell 使用户能够浏览 Node.js 应用程序的文件/文件夹路径(因为到目前为止我还没有找到更好的轻量级替代方案),而且我在处理可怕的、可怜的可用性 FolderBrowserDialog不支持:

  • 粘贴路径
  • 访问快速访问项目
  • 更改 View
  • 排序或过滤项目
  • 等等...

  • The bad dialog
    标准脚本如下所示:
    Function Select-FolderDialog($Description="Select Folder", $RootFolder="MyComputer"){
    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null

    $objForm = New-Object System.Windows.Forms.FolderBrowserDialog
    $objForm.RootFolder = $RootFolder
    $objForm.ShowNewFolderButton = $true
    $objForm.Description = "Please choose a folder"
    $Show = $objForm.ShowDialog()
    If ($Show -eq "OK")
    {
    Return $objForm.SelectedPath
    }
    Else
    {
    Write-Error "Operation cancelled by user."
    }
    }
    $folder = Select-FolderDialog
    write-host $folder
    我用过 Windows API CodePack为过去的 C# Windows 窗体应用程序创建一个 CommonOpenFileDialogIsFolderPicker = true ,让我了解 OpenFileDialog 的功能和可访问性易于使用的托管文件夹浏览器。
    Better, but non-standard dialog
    在我在这里寻找使用类似内容的方法时,我了解到常规的 FolderBrowserDialog got an upgrade, at least in .Net Core .
    呵呵,真巧。
    有什么办法可以访问升级版从 PowerShell 脚本?
    添加 $objForm.AutoUpgradeEnabled = $true上面的代码不会改变任何东西(确实是 the default )
    (另外,如果有人知道如何以更直接的方式为 Node.js 应用程序提供一个合适的文件夹浏览器对话框,请给我留言 ^^)
    到目前为止的解决方法:
    1:滥用 OpenFileDialog
    Function Select-FolderDialog($Description="Select Folder", $RootFolder="MyComputer"){
    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null

    $objForm = New-Object System.Windows.Forms.OpenFileDialog
    $objForm.DereferenceLinks = $true
    $objForm.CheckPathExists = $true
    $objForm.FileName = "[Select this folder]"
    $objForm.Filter = "Folders|`n"
    $objForm.AddExtension = $false
    $objForm.ValidateNames = $false
    $objForm.CheckFileExists = $false
    $Show = $objForm.ShowDialog()
    If ($Show -eq "OK")
    {
    Return $objForm.FileName
    }
    Else
    {
    Write-Error "Operation cancelled by user."
    }
    }

    $folder = Select-FolderDialog
    write-host $folder
    这将创建一个继承自更好的对话框 FileDialog Class ,它仅显示文件夹并允许您返回类似“C:\Some dir\Dir I want\[Select this folder]”的路径,即使它不存在,然后我可以将其修剪回“C:\一些目录\目录我想要”。
    优点:
  • 全功能浏览器,根据需要

  • 缺点:
  • 文件名字段不能为空。尝试使用类似
    换行符导致错误对话框提示
    无效的文件名和 FileOpenDialog拒绝退货
    文件名,即使 ValidateNamesfalse .
  • 用户可以在文件名字段中输入任何内容,这可能会导致混淆。
  • 当您向上浏览文件夹树时,单击接受按钮(“打开”)只会浏览回先前选择的子目录,即使您取消选择它
  • 您必须修剪掉返回路径的最后一部分,并希望不会因选择不存在的文件而发生进一步的怪异

  • 2:给一个标准对话框一个编辑控件
    使用 Shell.BrowseForFolder方法
    # Included the options values from https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfoa
    $BIF_RETURNONLYFSDIRS = [uint32]"0x00000001"
    $BIF_DONTGOBELOWDOMAIN = [uint32]"0x00000002"
    $BIF_STATUSTEXT = [uint32]"0x00000004"
    $BIF_RETURNFSANCESTORS = [uint32]"0x00000008"
    $BIF_EDITBOX = [uint32]"0x00000010" # <-- this is the important one
    $BIF_VALIDATE = [uint32]"0x00000020"
    $BIF_NEWDIALOGSTYLE = [uint32]"0x00000040" # <-- this sounds nice, but somehow changes nothing
    $BIF_BROWSEINCLUDEURLS = [uint32]"0x00000080"
    $BIF_USENEWUI = $BIF_NEWDIALOGSTYLE
    $BIF_UAHINT = [uint32]"0x00000100"
    $BIF_NONEWFOLDERBUTTON = [uint32]"0x00000200"
    $BIF_NOTRANSLATETARGETS = [uint32]"0x00000400"
    $BIF_BROWSEFORCOMPUTER = [uint32]"0x00001000"
    $BIF_BROWSEFORPRINTER = [uint32]"0x00002000"
    $BIF_BROWSEINCLUDEFILES = [uint32]"0x00004000"
    $BIF_SHAREABLE = [uint32]"0x00008000"
    $BIF_BROWSEFILEJUNCTIONS = [uint32]"0x00010000"

    $options = 0
    $options += $BIF_STATUSTEXT
    $options += $BIF_EDITBOX
    $options += $BIF_VALIDATE
    $options += $BIF_NEWDIALOGSTYLE
    $options += $BIF_BROWSEINCLUDEURLS
    $options += $BIF_SHAREABLE
    $options += $BIF_BROWSEFILEJUNCTIONS

    $shell = new-object -comobject Shell.Application
    $folder = $shell.BrowseForFolder(0, "Select a folder", $options)
    if($folder){
    write-host $folder.Self.Path()
    }
    为了清楚起见,我包含了这些选项,但您可以将上述所有内容硬编码到 $folder = $shell.BrowseForFolder(0, "Select a folder", 98548) 中。 ,这是整洁的。
    Look mom, auto-completing UNC paths
    优点:
  • 按预期使用文件夹浏览器对话框
  • 强大的用户体验
  • 可以粘贴路径
  • 支持 UNC 路径
  • 支持自动完成

  • 缺点:
  • 没有带有快速访问项等的侧面板
  • 无法更改 View 、排序等
  • 没有预览/缩略图
  • 最佳答案

    哇,您可以在 PowerShell 中使用 C#!
    环顾四周,我羡慕每个人都在 C# 中玩耍并利用我不知道如何在 PowerShell 中访问的酷功能。
    我喜欢 this approach ,例如,它不依赖于遗留 API 并且对不受支持的系统有一个后备。
    然后我看到您可以在 PowerShell 中使用实际的 C#!
    我将两者放在一起,稍微修改了代码以使其更容易从 PS 调用,然后出现了一种相当轻量级、希望稳健的方法来调用用户可用的最佳文件夹浏览器对话框:

    [Revised code below]
    我很想听听关于整个方法可能有多可靠的意见。
    无法访问某些引用或出现其他问题的可能性有多大?
    无论如何,我现在对这种方法很满意:)
    编辑:PowerShell“核心”(pwsh.exe;较新的程序集)
    所以,如 @mklement0在评论中指出,积极开发的 PowerShell(以前(以及以后,为了可读性)被称为“PowerShell Core”;与 Windows 附带的 Windows PowerShell 不同)似乎并不能很好地处理这个问题.
    在查看 PS Core 无益地仅报告为“ The type initializer for 'VistaDialog' threw an exception. ”(并添加对 System.ComponentModel.Primitives 的引用)后,结果发现 PS Core 倾向于使用较新版本的 System.Windows.Forms , 在我的情况下 5.0.4.0 , 不包含类型 FileDialogNative ,更不用说它的嵌套类型 IFileDialog .
    我试图强制它使用 Windows PS 引用的版本( 4.0.0.0 ),但它不遵守。
    好吧,我终于放下了一分钱,让 PS Core 简单地使用默认对话框, 这已经是我最初追求的升级版 😂
    因此,随着引入带有可选参数的构造函数方法的概念变化,我为失败的“Vista 对话框”添加了一个后备。
    我没有检查 PS 的版本或单个程序集和/或类/类型,而是简单地将调用包装在 try 中。/ catch堵塞。
    $path = $args[0]
    $title = $args[1]
    $message = $args[2]

    $source = @'
    using System;
    using System.Diagnostics;
    using System.Reflection;
    using System.Windows.Forms;
    /// <summary>
    /// Present the Windows Vista-style open file dialog to select a folder. Fall back for older Windows Versions
    /// </summary>
    #pragma warning disable 0219, 0414, 0162
    public class FolderSelectDialog {
    private string _initialDirectory;
    private string _title;
    private string _message;
    private string _fileName = "";

    public string InitialDirectory {
    get { return string.IsNullOrEmpty(_initialDirectory) ? Environment.CurrentDirectory : _initialDirectory; }
    set { _initialDirectory = value; }
    }
    public string Title {
    get { return _title ?? "Select a folder"; }
    set { _title = value; }
    }
    public string Message {
    get { return _message ?? _title ?? "Select a folder"; }
    set { _message = value; }
    }
    public string FileName { get { return _fileName; } }

    public FolderSelectDialog(string defaultPath="MyComputer", string title="Select a folder", string message=""){
    InitialDirectory = defaultPath;
    Title = title;
    Message = message;
    }

    public bool Show() { return Show(IntPtr.Zero); }

    /// <param name="hWndOwner">Handle of the control or window to be the parent of the file dialog</param>
    /// <returns>true if the user clicks OK</returns>
    public bool Show(IntPtr? hWndOwnerNullable=null) {
    IntPtr hWndOwner = IntPtr.Zero;
    if(hWndOwnerNullable!=null)
    hWndOwner = (IntPtr)hWndOwnerNullable;
    if(Environment.OSVersion.Version.Major >= 6){
    try{
    var resulta = VistaDialog.Show(hWndOwner, InitialDirectory, Title, Message);
    _fileName = resulta.FileName;
    return resulta.Result;
    }
    catch(Exception){
    var resultb = ShowXpDialog(hWndOwner, InitialDirectory, Title, Message);
    _fileName = resultb.FileName;
    return resultb.Result;
    }
    }
    var result = ShowXpDialog(hWndOwner, InitialDirectory, Title, Message);
    _fileName = result.FileName;
    return result.Result;
    }

    private struct ShowDialogResult {
    public bool Result { get; set; }
    public string FileName { get; set; }
    }

    private static ShowDialogResult ShowXpDialog(IntPtr ownerHandle, string initialDirectory, string title, string message) {
    var folderBrowserDialog = new FolderBrowserDialog {
    Description = message,
    SelectedPath = initialDirectory,
    ShowNewFolderButton = true
    };
    var dialogResult = new ShowDialogResult();
    if (folderBrowserDialog.ShowDialog(new WindowWrapper(ownerHandle)) == DialogResult.OK) {
    dialogResult.Result = true;
    dialogResult.FileName = folderBrowserDialog.SelectedPath;
    }
    return dialogResult;
    }

    private static class VistaDialog {
    private const string c_foldersFilter = "Folders|\n";

    private const BindingFlags c_flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
    private readonly static Assembly s_windowsFormsAssembly = typeof(FileDialog).Assembly;
    private readonly static Type s_iFileDialogType = s_windowsFormsAssembly.GetType("System.Windows.Forms.FileDialogNative+IFileDialog");
    private readonly static MethodInfo s_createVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("CreateVistaDialog", c_flags);
    private readonly static MethodInfo s_onBeforeVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("OnBeforeVistaDialog", c_flags);
    private readonly static MethodInfo s_getOptionsMethodInfo = typeof(FileDialog).GetMethod("GetOptions", c_flags);
    private readonly static MethodInfo s_setOptionsMethodInfo = s_iFileDialogType.GetMethod("SetOptions", c_flags);
    private readonly static uint s_fosPickFoldersBitFlag = (uint) s_windowsFormsAssembly
    .GetType("System.Windows.Forms.FileDialogNative+FOS")
    .GetField("FOS_PICKFOLDERS")
    .GetValue(null);
    private readonly static ConstructorInfo s_vistaDialogEventsConstructorInfo = s_windowsFormsAssembly
    .GetType("System.Windows.Forms.FileDialog+VistaDialogEvents")
    .GetConstructor(c_flags, null, new[] { typeof(FileDialog) }, null);
    private readonly static MethodInfo s_adviseMethodInfo = s_iFileDialogType.GetMethod("Advise");
    private readonly static MethodInfo s_unAdviseMethodInfo = s_iFileDialogType.GetMethod("Unadvise");
    private readonly static MethodInfo s_showMethodInfo = s_iFileDialogType.GetMethod("Show");

    public static ShowDialogResult Show(IntPtr ownerHandle, string initialDirectory, string title, string description) {
    var openFileDialog = new OpenFileDialog {
    AddExtension = false,
    CheckFileExists = false,
    DereferenceLinks = true,
    Filter = c_foldersFilter,
    InitialDirectory = initialDirectory,
    Multiselect = false,
    Title = title
    };

    var iFileDialog = s_createVistaDialogMethodInfo.Invoke(openFileDialog, new object[] { });
    s_onBeforeVistaDialogMethodInfo.Invoke(openFileDialog, new[] { iFileDialog });
    s_setOptionsMethodInfo.Invoke(iFileDialog, new object[] { (uint) s_getOptionsMethodInfo.Invoke(openFileDialog, new object[] { }) | s_fosPickFoldersBitFlag });
    var adviseParametersWithOutputConnectionToken = new[] { s_vistaDialogEventsConstructorInfo.Invoke(new object[] { openFileDialog }), 0U };
    s_adviseMethodInfo.Invoke(iFileDialog, adviseParametersWithOutputConnectionToken);

    try {
    int retVal = (int) s_showMethodInfo.Invoke(iFileDialog, new object[] { ownerHandle });
    return new ShowDialogResult {
    Result = retVal == 0,
    FileName = openFileDialog.FileName
    };
    }
    finally {
    s_unAdviseMethodInfo.Invoke(iFileDialog, new[] { adviseParametersWithOutputConnectionToken[1] });
    }
    }
    }

    // Wrap an IWin32Window around an IntPtr
    private class WindowWrapper : IWin32Window {
    private readonly IntPtr _handle;
    public WindowWrapper(IntPtr handle) { _handle = handle; }
    public IntPtr Handle { get { return _handle; } }
    }

    public string getPath(){
    if (Show()){
    return FileName;
    }
    return "";
    }
    }
    '@
    Add-Type -Language CSharp -TypeDefinition $source -ReferencedAssemblies ("System.Windows.Forms", "System.ComponentModel.Primitives")
    ([FolderSelectDialog]::new($path, $title, $message)).getPath()
    这应该适用于 Windows PowerShell(最终版本 ~ 5.1 )和当前的 PS“核心”( pwsh.exe ~ 7.1.3 )

    关于.net - 在 Powershell 中使用升级的 FolderBrowserDialog ("Vista style"),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66823581/

    35 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com