John NoCookies
John NoCookies

Reputation: 1061

Refresing the C# DataGrid from a background thread

I'm using a DataGrid bound to an ObservableCollection source to display two columns, a file name and a number that I get from analysing the file.

    ObservableCollection<SearchFile> fileso;

    //...

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        /* Get all the files to check. */
        int dirCount = searchFoldersListView.Items.Count;
        List<string> allFiles = new List<string>();
        for(int i = 0; i < dirCount; i++)
        {
            try
            {
                allFiles.AddRange(Directory.GetFiles(searchFoldersListView.Items[i].ToString(), "*.txt").ToList());
                allFiles.AddRange(Directory.GetFiles(searchFoldersListView.Items[i].ToString(), "*.pdf").ToList());
            }
            catch
            { /* stuff */ }
        }

        /* Clear the collection and populate it with unchecked files again, refreshing the grid. */
        this.Dispatcher.Invoke(new Action(delegate
        {
            fileso.Clear();
            foreach(var file in allFiles)
            {
                SearchFile sf = new SearchFile() { path=file, occurrences=0 };
                fileso.Add(sf);
            }
        }));

        /* Check the files. */
        foreach(var file in allFiles)
        {
            this.Dispatcher.Invoke(new Action(delegate
            {
                int occurences;
                bool result = FileSearcher.searchFile(file, searchTermTextBox.Text, out occurences);

                fileso.AddOccurrences(file, occurences);  // This is an extension method that alters the collection by finding the relevant item and changing it.
            }));
        }
    }

    //...

    public static void AddOccurrences(this ObservableCollection<SearchFile> collection, string path, int occurrences)
    {
        for(int i = 0; i < collection.Count; i++)
        {
            if(collection[i].path == path)
            {
                collection[i].occurrences = occurrences;
                break;
            }
        }
    }

    //...

    public static bool searchTxtFile(string path, string term, out int occurences)
    {
        string contents = File.ReadAllText(path);
        occurences = Regex.Matches(contents, term, RegexOptions.IgnoreCase).Count;
        if(occurences>0)
            return true;
        return false;
    }

    public static bool searchDocxFile(string path, string term, out int occurences)
    {
        occurences = 0;

        string tempPath = Path.GetTempPath();
        string rawName = Path.GetFileNameWithoutExtension(path);
        string destFile = System.IO.Path.Combine(tempPath, rawName + ".zip");
        System.IO.File.Copy(path, destFile, true);

        using(ZipFile zf = new ZipFile(destFile))
        {
            ZipEntry ze = zf.GetEntry("word/document.xml");
            if(ze != null)
            {
                using(Stream zipstream = zf.GetInputStream(ze))
                {
                    using(StreamReader sr = new StreamReader(zipstream))
                    {
                        string docContents = sr.ReadToEnd();
                        string rawText = Extensions.StripTagsRegexCompiled(docContents);
                        occurences = Regex.Matches(rawText, term, RegexOptions.IgnoreCase).Count;
                        if(occurences>0)
                            return true;
                        return false;
                    }
                }
            }
        }
        return false;
    }

    public static bool searchFile(string path, string term, out int occurences)
    {
        occurences = 0;
        string ext = System.IO.Path.GetExtension(path);

        switch(ext)
        {
            case ".txt":
                return searchTxtFile(path, term, out occurences);
            //case ".doc":
            //    return searchDocFile(path, term, out occurences);
            case ".docx":
                return searchDocxFile(path, term, out occurences);
        }
        return false;
    }

But the problem is that sometimes, when I hit the update button (which starts the worker with the the do_work method above), some of the time, I'm getting random zeroes in the number column instead of the correct number. Why is that? I'm assuming it's because there's some problem with updating the number column twice, with sometimes the first zeroing getting applied after the actual update, but I'm not sure about the details.

Upvotes: 0

Views: 305

Answers (1)

Dtex
Dtex

Reputation: 2623

I think this is a case of an access to a modified closure

        /* Check the files. */
        foreach(var file in allFiles)
        {
            var fileTmp = file; // avoid access to modified closure
            this.Dispatcher.Invoke(new Action(delegate
            {
                int occurences;
                bool result = FileSearcher.searchFile(fileTmp, searchTermTextBox.Text, out occurences);

                fileso.AddOccurrences(fileTmp, occurences);  // This is an extension method that alters the collection by finding the relevant item and changing it.
            }));
        }

Basically what's happening is that you are passing the file variable to a lambda expression, but file will be modified by the foreach loop before the action is actually invoked, using a temp variable to hold file should solve this.

Upvotes: 1

Related Questions