Search code examples
c++wxwidgets

File list widget


I want a list widget that is a flat view of just files in a single directory. It should be monitored in real time and ideally I should be able to disable it updating itself temporarily if a lot is going to happen in a short period of time. Is there a standard widget that does that? A quick search brings up wxGenericDirCtrl but, as I understand, there is no way for it to show just files (not directories) of a single directory. It's more of a tree than a list.

Otherwise, what's the best way to make something similar. I can probably subclass wxListBox and use wxFileSystemWatcher to keep the view in sync, but it will be tedious. Is there a better way?

I am on Windows; portability is not a concern. I target Windows 7 to 10.


Solution

  • As Igor said above, given the requirements you stated, I don' think there is any other way to do this than to use a wxFileSystemWatcher.

    I pulled together an example of how to keep a list box updated based on reports from the file system watcher. I have to admit that this did turn out to be a more complicated than I thought it would, but I don't think it's excessively tedious.

    // For compilers that support precompilation, includes "wx/wx.h".
    #include "wx/wxprec.h"
    
    #ifdef __BORLANDC__
        #pragma hdrstop
    #endif
    
    // for all others, include the necessary headers (this file is usually all you
    // need because it includes almost all "standard" wxWidgets headers)
    #ifndef WX_PRECOMP
        #include "wx/wx.h"
    #endif
    
    #include <wx/fswatcher.h>
    #include <wx/filepicker.h>
    #include <vector>
    
    class MyFrame: public wxFrame
    {
        public:
            MyFrame();
        private:
            // Event handlers
            void OnWatcher(wxFileSystemWatcherEvent& event);
            void OnDirChanged(wxFileDirPickerEvent& event);
    
            // Helper functions
            wxString StripPath(const wxString&);
            void DeleteFromListbox(const wxString&);
    
            wxFileSystemWatcher m_watcher;
            wxListBox* m_listBox;
    };
    
    MyFrame::MyFrame()
            :wxFrame(NULL, wxID_ANY, "Filesystem watcher frame", wxDefaultPosition,
                     wxSize(600, 400))
    {
        // Set up the UI elements.
        wxPanel* mainPanel = new wxPanel(this,wxID_ANY);
        wxDirPickerCtrl* dirPicker = new wxDirPickerCtrl(mainPanel, wxID_ANY);
        m_listBox = new wxListBox(mainPanel, wxID_ANY);
    
        wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
        sizer->Add(dirPicker, wxSizerFlags(0).Expand().Border(wxALL));
        sizer->Add(m_listBox, wxSizerFlags(1).Expand().Border(wxALL&~wxTOP));
    
        mainPanel->SetSizer(sizer);
        Layout();
    
        // Set up the event handlers.
        m_watcher.SetOwner(this);
        dirPicker->Bind(wxEVT_DIRPICKER_CHANGED, &MyFrame::OnDirChanged, this);
        Bind(wxEVT_FSWATCHER, &MyFrame::OnWatcher, this);
    }
    
    void MyFrame::OnWatcher(wxFileSystemWatcherEvent& event)
    {
        wxFileName name = event.GetPath();
    
        // Ignore all watch events for folders.
        if ( name.IsDir() )
        {
            return;
        }
    
        wxFileName newName = event.GetNewPath();
        int changeType = event.GetChangeType();
    
        switch ( changeType )
        {
            case wxFSW_EVENT_CREATE :
                m_listBox->Append( StripPath(name.GetFullPath()) );
                break;
            case wxFSW_EVENT_DELETE :
                DeleteFromListbox( StripPath(name.GetFullPath()) );
                break;
            case wxFSW_EVENT_RENAME :
                DeleteFromListbox( StripPath(name.GetFullPath()) );
                m_listBox->Append( StripPath(newName.GetFullPath()) );
                break;
            case wxFSW_EVENT_MODIFY :
                wxFALLTHROUGH;
            case wxFSW_EVENT_ACCESS :
                wxFALLTHROUGH;
            case wxFSW_EVENT_ATTRIB :
                wxFALLTHROUGH;
    #ifndef __WINDOWS__
            case wxFSW_EVENT_UNMOUNT :
                wxFALLTHROUGH;
    #endif // __WINDOWS__
            case wxFSW_EVENT_WARNING :
                wxFALLTHROUGH;
            case wxFSW_EVENT_ERROR  :
                wxFALLTHROUGH;
            default:
                break;
        }
    }
    
    void MyFrame::OnDirChanged(wxFileDirPickerEvent& event)
    {
        if ( !::wxDirExists(event.GetPath()) )
        {
            return;
        }
        
        // Remove the current folder from the watch and add the new one.
        m_watcher.RemoveAll();
    
        wxString pathWithSep = event.GetPath();
        if ( pathWithSep.Right(1) != wxFileName::GetPathSeparator() )
        {
            pathWithSep << wxFileName::GetPathSeparator();
        }
        m_watcher.Add(wxFileName(pathWithSep));
    
        // Get a list of the files in new folder.
        std::vector<wxString> files;
        wxDir dir(pathWithSep);
        wxString curName;
        if ( dir.GetFirst(&curName, "*", wxDIR_FILES) )
        {
            files.push_back(StripPath(curName));
        }
        while ( dir.GetNext(&curName) )
        {
            files.push_back(StripPath(curName));
        }
    
        // Replace the current contents of the listbox with the new filenames.
        m_listBox->Clear();
        for ( auto it = files.begin() ; it != files.end() ; ++it )
        {
            m_listBox->Append(*it);
        }
    }
    
    wxString MyFrame::StripPath(const wxString& name)
    {
        wxFileName fn(name);
        return fn.GetFullName();
    }
    
    void MyFrame::DeleteFromListbox(const wxString& name)
    {
        int index = m_listBox->FindString(name);
        if ( index != wxNOT_FOUND )
        {
            m_listBox->Delete(static_cast<unsigned int>(index));
        }
    }
    
    class MyApp : public wxApp
    {
        public:
            virtual bool OnInit()
            {
                MyFrame* frame = new MyFrame();
                frame->Show();
                return true;
            }
    };
    
    wxIMPLEMENT_APP(MyApp);
    
    

    This is just a simple example. There is some room for improvement:

    1. I'm not sure what would happen if changes are made to the files in the folder while enumeration is in progress. If this turns out to be an issue, I guess you could collect the watcher events during the enumeration and then resolve them after it is complete.
    2. If the folders being enumerated can contain many files, it would probably be best to do the enumeration in a secondary thread,
    3. A list control might be more suitable than a list box if you want to show an icon or metadata. A list control will also allow the list to be sorted.