Встраивание экземпляра File Explorer в форму приложения Windows Forms - PullRequest
12 голосов
/ 12 февраля 2009

Мое приложение (C #, .NET 3.5) создает файлы, и в дополнение к возникновению событий, которые можно перехватить и отреагировать, я хочу отобразить целевую папку для пользователя в форме. Список файлов отображается в той же форме, что и другая информация.

Я использую экземпляр элемента управления WebBrowser (System.Windows.Forms.WebBrowser), затем перехожу к папке. Это показывает некоторое представление по умолчанию окна обозревателя с панелью сводки файлов слева и файлами в представлении «Плитки» (большой значок и текст).

Например,

wb.Navigate(@"c:\path\to\folder\");

Я хочу отключить панель и просмотреть список файлов в представлении «Подробности». Пользователь может добраться до этого через контекстное меню, вызываемое правой кнопкой мыши, но я бы хотел, чтобы оно появлялось автоматически.

Я бы предпочел не создавать свой собственный TreeView, DataGridView или любой другой; элемент управления WebBrowser выполняет все обновления и повторную сортировку и еще много чего бесплатно.

Есть ли лучший способ? Другой используемый элемент управления или дополнительные аргументы для передачи элементу управления?

И если бы я мог перехватывать события (например, файлы, которые выбираются / переименовываются / дважды щелкают и т. Д.), Тогда все будет лучше!

Ответы [ 8 ]

9 голосов
/ 22 февраля 2009

ВНИМАНИЕ: Длинный пост с большим количеством кода.

Когда вы перемещаете элемент управления веб-браузера в папку файловой системы, элемент управления веб-браузера размещает окно представления оболочки, которое, в свою очередь, содержит представление списка проводника. Фактически это то же самое, что процесс Explorer, а также диалоги файлов и Internet Explorer. Это окно оболочки не является элементом управления, поэтому в нем нет методов, которые могут быть вызваны, или события, на которые можно подписаться, но оно может получать сообщения Windows и может быть разделено на подклассы.

Оказывается, что часть вашего вопроса, касающаяся автоматической настройки представления в деталях, на самом деле довольно проста. В событии Navigated элемента управления вашего веб-браузера просто найдите дескриптор окна просмотра оболочки и отправьте ему сообщение WM_COMMAND с определенной константой оболочки (SHVIEW_REPORT). Это недокументированная команда, но она поддерживается на всех платформах Windows до Windows 2008 включительно и почти наверняка будет на Windows 7. Некоторый код, добавляемый в форму вашего веб-браузера, демонстрирует это:

    private delegate int EnumChildProc(IntPtr hwnd, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
        IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern int EnumChildWindows(IntPtr hWndParent,
        EnumChildProc lpEnumFunc, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName,
        int nMaxCount);

    private const int WM_COMMAND = 0x0111;
    private const int SHVIEW_REPORT = 0x702C;
    private const string SHELLVIEW_CLASS = "SHELLDLL_DefView";

    private IntPtr m_ShellView;

    void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
    {
        m_ShellView = IntPtr.Zero;
        EnumChildWindows(webBrowser1.Handle, EnumChildren, IntPtr.Zero);
        if (m_ShellView != IntPtr.Zero)
        {
            SendMessage(m_ShellView, WM_COMMAND, (IntPtr)SHVIEW_REPORT, (IntPtr)0);
        }
    }

    private int EnumChildren(IntPtr hwnd, IntPtr lParam)
    {
        int retval = 1;

        StringBuilder sb = new StringBuilder(SHELLVIEW_CLASS.Length + 1);
        int numChars = GetClassName(hwnd, sb, sb.Capacity);
        if (numChars == SHELLVIEW_CLASS.Length)
        {
            if (sb.ToString(0, numChars) == SHELLVIEW_CLASS)
            {
                m_ShellView = hwnd;
                retval = 0;
            }
        }

        return retval;
    }

Каждый раз, когда веб-браузер переходит в новое окно (в том числе, когда папка открывается из представления проводника), создается новое окно просмотра оболочки, поэтому сообщение необходимо повторно отправлять в новое окно при каждом событии Navigated.

Для второй части вашего вопроса вы хотели бы получать события из представления списка проводника. Это немного сложнее, чем в первой части. Чтобы сделать это, вам нужно разделить окно просмотра списка на подклассы, а затем отслеживать сообщения окон за интересующими вас сообщениями (например, WM_LBUTTONDBLCLK). Чтобы создать подкласс окна, вам нужно создать свой собственный класс, производный от класса NativeWindow, и назначить ему дескриптор окна, которое нужно отслеживать. Затем вы можете переопределить процедуру Window и обрабатывать различные сообщения по своему усмотрению. Ниже приведен пример создания события двойного щелчка - это относительно просто, но для получения расширенного доступа к представлению списка проводника может потребоваться гораздо больше работы, чем вы готовы.

Добавьте это к своей форме:

    private ExplorerListView m_Explorer;

    void OnExplorerItemExecuted(object sender, ExecuteEventArgs e)
    {
        string msg = string.Format("Item to be executed: {0}{0}{1}", 
            Environment.NewLine, e.SelectedItem);
        e.Cancel = (MessageBox.Show(msg, "", MessageBoxButtons.OKCancel) 
            == DialogResult.Cancel);
    }

и эти две строки в обработчик события Navigated (сразу после SendMessage):

    m_Explorer = new ExplorerListView(m_ShellView);
    m_Explorer.ItemExecuted += OnExplorerItemExecuted;

Затем добавьте следующие классы:

class ExplorerListView : NativeWindow
{

    public event EventHandler<ExecuteEventArgs> ItemExecuted;

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
        IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr FindWindowEx(IntPtr hwndParent,
        IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

    private const int WM_LBUTTONDBLCLK = 0x0203;

    private const int LVM_GETNEXTITEM = 0x100C;
    private const int LVM_GETITEMTEXT = 0x1073;

    private const int LVNI_SELECTED = 0x0002;

    private const string EXPLORER_LISTVIEW_CLASS = "SysListView32";

    public ExplorerListView(IntPtr shellViewHandle)
    {
        base.AssignHandle(FindWindowEx(shellViewHandle, IntPtr.Zero, 
            EXPLORER_LISTVIEW_CLASS, null));
        if (base.Handle == IntPtr.Zero)
        {
            throw new ArgumentException("Window supplied does not encapsulate an explorer window.");
        }
    }


    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_LBUTTONDBLCLK:
                if (OnItemExecution() != 0) return;
                break;
            default:
                break;
        }
        base.WndProc(ref m);
    }

    private int OnItemExecution()
    {
        int cancel = 0;
        ExecuteEventArgs args = new ExecuteEventArgs(GetSelectedItem());
        EventHandler<ExecuteEventArgs> temp = ItemExecuted;
        if (temp != null)
        {
            temp(this, args);
            if (args.Cancel) cancel = 1;
        }
        return cancel;
    }

    private string GetSelectedItem()
    {
        string item = null;

        IntPtr pStringBuffer = Marshal.AllocHGlobal(2048);
        IntPtr pItemBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LVITEM)));

        int selectedItemIndex = SendMessage(base.Handle, LVM_GETNEXTITEM, (IntPtr)(-1), (IntPtr)LVNI_SELECTED).ToInt32();
        if (selectedItemIndex > -1)
        {
            LVITEM lvi = new LVITEM();
            lvi.cchTextMax = 1024;
            lvi.pszText = pStringBuffer;
            Marshal.StructureToPtr(lvi, pItemBuffer, false);
            int numChars = SendMessage(base.Handle, LVM_GETITEMTEXT, (IntPtr)selectedItemIndex, pItemBuffer).ToInt32();
            if (numChars > 0)
            {
                item = Marshal.PtrToStringUni(lvi.pszText, numChars);
            }
        }

        Marshal.FreeHGlobal(pStringBuffer);
        Marshal.FreeHGlobal(pItemBuffer);

        return item;
    }

    struct LVITEM
    {
        public int mask;
        public int iItem;
        public int iSubItem;
        public int state;
        public int stateMask;
        public IntPtr pszText;
        public int cchTextMax;
        public int iImage;
        public IntPtr lParam;
        public int iIndent;
        public int iGroupId;
        int cColumns; // tile view columns
        public IntPtr puColumns;
        public IntPtr piColFmt;
        public int iGroup;

    }
}

public class ExecuteEventArgs : EventArgs
{
    public string SelectedItem { get; private set; }
    public bool Cancel { get; set; }

    internal ExecuteEventArgs(string selectedItem)
    {
        SelectedItem = selectedItem;
    }
}

Это должно дать вам представление о том, что вам нужно будет сделать. Если вы хотите более чем достаточно простые события, вы можете искать альтернативный элемент управления, хотя из того, что я видел в свободных и недорогих областях, есть несколько довольно приличных элементов управления, но все они имеют некоторые причуды и не дают плавного исследователя. опыт.

Помните, что этот код был составлен довольно быстро без обработки ошибок или комментариев и игнорирования нескольких проблем, таких как несколько выбранных элементов, поэтому используйте его в качестве руководства и на свой страх и риск.

4 голосов
/ 21 февраля 2009

Чтобы справиться с переименованием, удалением и другими настройками, вам нужно написать собственный файловый менеджер. Элемент управления WebBrowser не подходит для ваших нужд. Это просто оболочка над компонентом ActiveX.
Вы должны проверить эту статью проекта кода . Он содержит реализацию файлового проводника. Есть еще несколько примеров файлового браузера:
один
два

3 голосов
/ 28 апреля 2009

Я написал библиотеку, которая может вам помочь. Вы можете найти его по адресу: http://gong -shell.sourceforge.net /

Элемент управления, который вы ищете, - ShellView. Там также есть учебники о том, как создать простой клон Windows Explorer в несколько строк.

Примечание для пользователей .NET 4.0 : Gong-shell в настоящее время не работает для 4.0. Фреймворк внес изменения в Interop, и он будет прекрасно работать, но будет вызывать различные проблемы при взаимодействии с shell32 (особенно API-интерфейс shellicon, приводящий к разыменованию неуправляемого нулевого указателя).

3 голосов
/ 22 февраля 2009

Программное обеспечение LogicNP имеет два элемента управления (FileView и ShComboBox), которые выполняют то, что вы ищете: http://www.ssware.com/fldrview.htm

Вы можете загрузить пробную версию с их страницы, однако стоимость лицензии составляет ~ 130 $.

1 голос
/ 31 декабря 2009

Возможно, вы захотите взглянуть на объект ExplorerBrowser.

Подробнее см. http://blogs.msdn.com/ieinternals/archive/2009/12/30/Windows-7-Web-Browser-Control-will-not-browse-file-system.aspx.

1 голос
/ 22 апреля 2009

Ознакомьтесь с этой статьей здесь , в ней показано, как это сделать в .NET и WinForms. Это дает полный контроль над тем, что видит пользователь.

Я использовал его в одном из своих приложений, и он работает очень хорошо. Вы можете отобразить значок / подробности / представление списка, и это остановит переход пользователя в другие каталоги (что часто является проблемой при отображении стандартных диалоговых окон файла / каталога.

Я использую его, чтобы показать экран, как показано ниже ниже http://img7.imageshack.us/img7/7647/screenshotbaf.png:

1 голос
/ 24 февраля 2009

Если вы довольны только Windows Vista и включением COM элемента управления, IExplorerBrowser может быть приемлемым для вас.

В этой статье Code Project показано ее использование в MFC программе, но хотя бы еще один человек , кажется, заставил его работать в C # после некоторого усилие.

Более новый API предоставляет значительно больше возможностей программирования, чем просто перехват сообщений, но он (очевидно) бесполезен для старых платформ.

0 голосов
/ 21 февраля 2009

Если вы хотите открыть другое окно для отображения содержимого целевой папки, вы можете использовать System.Windows.Forms.OpenFileDialog или SaveFileDialog или унаследовать от FileDialog и расширить его.

Чтобы позволить пользователю выбрать папку, вы можете использовать FolderBrowserDialog, хотя, как пользователь, мне не нравится этот элемент управления.

Помогает ли это, или вам абсолютно необходимо встроить элемент управления в форму?

Асаф

...