Click here to Skip to main content
15,897,334 members
Articles / Programming Languages / C#

Find File in Solution

Rate me:
Please Sign up or sign in to vote.
5.00/5 (23 votes)
28 Mar 2010CPOL6 min read 112.7K   3K   48  
Find any file in solution using incremental search and advanced filtering
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;

namespace SlnFileFindAddIn
{
    public partial class SlnFileFind : UserControl
    {
        public SlnFileFind()
        {
            InitializeComponent();
            InitializeComponent2();
            InitializeComparators();
            SubscribeToEvents();
        }

        ~SlnFileFind()
        {
            UnsubscribeFromEvents();
        }

        public void Activate()
        {
            this.ActiveControl = _txtSearch;
            FileFindWnd.Activate();
        }

        public void Activate(string filterText)
        {
            Activate();

            if (filterText.Length > 0)
            {
                _txtSearch.Text = filterText;
                ApplyFilter();
            }
        }

        public void ResetSearchText()
        {
            _txtSearch.Clear();
        }

        public string CurrentFilter
        {
            get { return _txtSearch.Text; }
        }

        void ApplyFilter()
        {
            // Virtual list-view can operate only on indices,
            // cannot access SelectedItems/CheckedItems collections.
            _lvwFiles.SelectedIndices.Clear();

            SlnFileFind.ProjectItems.ApplyFilter(_txtSearch.Text);

            if (_lvwFiles.VirtualListSize > 0)
            {
                _lvwFiles.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
            }
        }

        void SubscribeToEvents()
        {
            SlnFileFind.ProjectItems.CollectionChanged +=
                new SlnProjectItems.CollectionChangedHandler(
                    SlnProjectItems_CollectionChanged);

            SlnProjectItems_CollectionChanged();
        }

        void UnsubscribeFromEvents()
        {
            SlnFileFind.ProjectItems.CollectionChanged -=
                new SlnProjectItems.CollectionChangedHandler(
                    SlnProjectItems_CollectionChanged);
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            bool handled = false;
            switch (keyData)
            {
                case Keys.Return:
                    OpenSelectedItems();
                    handled = true;
                    break;
                case Keys.Escape:
                    CloseMyself();
                    handled = true;
                    break;
                default:
                    handled = base.ProcessDialogKey(keyData);
                    break;
            }

            return handled;
        }

        void SlnProjectItems_CollectionChanged()
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(
                    new MethodInvoker(SlnProjectItems_CollectionChanged));
            }
            else
            {
                SetListSize(SlnFileFind.ProjectItems.Count);
                Invalidate(/*invalidateChildren=*/true);
            }
        }

        private void SetListSize(int size)
        {
            // ListView bug workaround;
            // Google for
            //  NullReferenceException ArgumentOutOfRangeException VirtualListSize
            try
            {
                _lvwFiles.VirtualListSize = 0;
                _lvwFiles.VirtualListSize = size;
            }
            catch (ArgumentOutOfRangeException e)
            {
                Debug.WriteLine("ListView.VirtualListSize: " + e);
            }
            catch (NullReferenceException e)
            {
                Debug.WriteLine("ListView.VirtualListSize: " + e);
            }
        }

        void LvwFiles_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            lock (SlnFileFind.ProjectItems)
            {
                //Debug.Assert(SlnFileFind.ProjectItems.Count > e.ItemIndex);

                if (SlnFileFind.ProjectItems.Count > e.ItemIndex)
                {
                    SlnProjectItem item = SlnFileFind.ProjectItems[e.ItemIndex];

                    e.Item = new ListViewItem(item.fileName, item.iconIndex);
                    e.Item.SubItems.Add(item.ProjectName);
                    e.Item.SubItems.Add(item.fullPath);
                    e.Item.ToolTipText = item.fullPath + (item.MultipleProjects ?
                        "\r\n_____\r\n" + item.FullProjectName : "");
                }
                else
                {   // If we get here, it means that internal collection size is
                    // out of sync with list-view virtual size.
                    // List-view virtual size is bigger than the number of
                    // actual items we have.
                    // It happens very rarely during shutdown.
                    e.Item = new ListViewItem();
                }
            }
        }

        void LvwFiles_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            SortOrder newSortOrder = GetOppositeSortOrder(e.Column);

            bool sorted = SlnFileFind.ProjectItems.Sort(
                new SlnProjectItemCompare(
                    newSortOrder,
                    _columnSortInfo[e.Column].itemOrder)
            );

            if (sorted)
            {
                SetSortOrder(e.Column, newSortOrder);
                UpdateColumnSortMark(_columnSortInfo[e.Column].sortOder, e.Column);
            }
        }

        void LvwFiles_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Tab:
                    GetNextControl((Control)sender, !e.Shift).Focus();
                    break;
            }
        }

        private void LvwFiles_DoubleClick(object sender, EventArgs e)
        {
            OpenSelectedItems();
        }

        private void LvwFiles_SelectFirstItem()
        {
            if (_lvwFiles.Items.Count > 0)
            {
                if(_lvwFiles.SelectedIndices.Count > 0)
                    _lvwFiles.SelectedIndices.Clear();

                _lvwFiles.Items[0].Focused = true;
                _lvwFiles.Items[0].Selected = true;
            }
        }

        private void TimerSearch_Tick(object sender, EventArgs e)
        {
            _timerSearch.Stop();

            ApplyFilter();
        }

        private void LvwFiles_FocusEnter(object sender, EventArgs e)
        {
            if (_lvwFiles.FocusedItem != null)
                _lvwFiles.FocusedItem.Selected = true;
            else
                LvwFiles_SelectFirstItem();
        }

        void TxtSearch_TextChanged(object sender, EventArgs e)
        {
            _timerSearch.Start();
        }

        void TxtSearch_EnterFocus(object sender, EventArgs e)
        {
            _txtSearch.SelectAll();
        }

        void TxtSearch_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Down:
                    LvwFiles_SelectFirstItem();
                    _lvwFiles.Focus();
                    break;
                case Keys.Enter:
                    if (e.Control && String.Compare(_txtSearch.Text, "settings",
                            StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        e.Handled = true;
                        ShowSettings();
                    }
                    break;
            }
        }

        void ShowSettings()
        {
            Settings settings = new Settings();
            settings.SearchDelay = _timerSearch.Interval;
            DialogResult dlgRes = settings.ShowDialog(
                new Win32Window(FileFindWnd.HWnd));

            if (dlgRes == DialogResult.OK)
                _timerSearch.Interval = settings.SearchDelay;

            if (settings.RescanSolution)
                RescanSolution();
        }

        void RescanSolution()
        {
            if (Application == null) return;

            SlnFileFind.ProjectItems.RetrieveSolutionItems(Application);
            SlnFileFind.ProjectItems.ApplyFilter(_txtSearch.Text);
        }

        void OpenSelectedItems()
        {
            List<string> missingFiles = new List<string>();
            Window wnd = null;

            lock (SlnFileFind.ProjectItems)
            {
                // We need to lock SlnFileFind.ProjectItems collection during opening
                // because VS IDE may add/change solution tree items as a result of
                // opened files.
                // Upon change solution tree items and projects will trigger
                // corresponding events and our code will try to recreate
                // SlnFileFind.ProjectItems collection. So, the collection of
                // prject items will die while we're trying to open its items;
                // ListView control quickly gets out of sync with SlnFileFind.ProjectItems
                // and we get a crash. Hence the lock.

                foreach (int i in _lvwFiles.SelectedIndices)
                {
                    if (SlnFileFind.ProjectItems.Count <= i)
                        continue; // see comments in LvwFiles_RetrieveVirtualItem

                    SlnProjectItem slnItem = SlnFileFind.ProjectItems[i];

                    if (System.IO.File.Exists(slnItem.fullPath))
                    {
                        try
                        {
                            wnd = Application.ItemOperations.OpenFile(
                                slnItem.fullPath, Constants.vsViewKindPrimary);
                            wnd.Activate();
                        }
                        catch (System.IO.IOException)
                        {
                            missingFiles.Add(slnItem.fullPath);
                        }
                    }
                    else missingFiles.Add(slnItem.fullPath);
                }
            }

            if (missingFiles.Count > 0)
            {
                StringBuilder sb = new StringBuilder(
                    "Some of the files could not be opened:\r\n\r\n");

                foreach (string filePath in missingFiles)
                    sb.AppendLine(filePath);

                MessageBox.Show(this, sb.ToString(), "Find File in Solution",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

            if (wnd != null) // at least one document was opened
                CloseMyself();
        }

        new Control GetNextControl(Control ctl, bool forward)
        {
            Control ctlNext = null;

            // attempt base methos
            ctlNext = base.GetNextControl(ctl, forward);

            // we're at the beginning or end of the controls chain
            if (ctlNext == null)
            {
                int maxTabIndex = 0;
                int maxTabIndexCtl = -1;
                int minTabIndex = this.Controls.Count;
                int minTabIndexCtl = -1;

                for(int index = 0; index < this.Controls.Count; ++index)
                {
                    Control c = this.Controls[index];

                    if (maxTabIndex < c.TabIndex && c.TabStop)
                    {
                        maxTabIndex = c.TabIndex;
                        maxTabIndexCtl = index;
                    }

                    if (minTabIndex > c.TabIndex && c.TabStop)
                    {
                        minTabIndex = c.TabIndex;
                        minTabIndexCtl = index;
                    }
                }

                int nextIndex = (forward ? minTabIndexCtl : maxTabIndexCtl);

                ctlNext = (nextIndex != -1 ? this.Controls[nextIndex] : ctl);
            }

            return ctlNext;
        }

        void InitializeComparators()
        {
            _columnSortInfo = new ColumnSortInfo[3] {
                new ColumnSortInfo(SortOrder.Ascending, SlnProjectItemOrder.ByItemName),
                new ColumnSortInfo(SortOrder.Ascending, SlnProjectItemOrder.ByProjectName),
                new ColumnSortInfo(SortOrder.Ascending, SlnProjectItemOrder.ByItemFullPath)
            };
        }

        void SetSortOrder(int columnIndex, SortOrder newSortOrder)
        {
            _columnSortInfo[columnIndex].sortOder = newSortOrder;
        }

        SortOrder GetOppositeSortOrder(int columnIndex)
        {
            return _columnSortInfo[columnIndex].sortOder == SortOrder.Ascending ?
                    SortOrder.Descending : SortOrder.Ascending;
        }

        void CloseMyself()
        {
            if (FileFindWnd != null && FileFindWnd.IsFloating)
                FileFindWnd.Close(vsSaveChanges.vsSaveChangesNo);
        }

        struct ColumnSortInfo
        {
            public ColumnSortInfo(SortOrder so, SlnProjectItemOrder io)
            {
                sortOder = so;
                itemOrder = io;
            }

            public SortOrder sortOder;
            public SlnProjectItemOrder itemOrder;
        };

        ColumnSortInfo[] _columnSortInfo;
        internal static SlnProjectItems ProjectItems = new SlnProjectItems();

        // The following properties will be set up by code that calls
        // Windows2.CreateToolWindow2. We may need them in the future.
        public DTE2 Application;
        public AddIn AddInInstance;
        public Window FileFindWnd;
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Australia Australia
More than ten years of C++ native development, and counting.

Smile | :)

Comments and Discussions