Suchfunktion - funktioniert mit Einschränkungen

Memfis

Erfahrenes Mitglied
Ich bin (schon etwas länger) dabei eine kleine Suchfunktion zu basteln. Sie funktioniert jetzt, aber es gibt leider erhebliche Einschränkungen und ich hoffe diese mit eurer Hilfe lösen zu können. Ich muss dazu sagen, dass ich kein Programmierer bin und meine Schwerpunkte in anderen Bereichen vorhanden sind. Daher hoffe ich auf stärkere Hilfe als nur kleine Denkanstöße. (Das heißt aber nicht, dass ihr mir den Code vorkauen sollt)

1) Die Suche funktioniert nur auf Netzlaufwerken. Auf den Laufwerken direkt am Computer (also beispielsweise c:\) gibt es einen Fehler. Wieso?
2) Es dauert ewig bis ein Ergebnis angezeigt wird. Ich hätte gerne, dass gefundene Dateien direkt angezeigt werden, so wie bei der windowseigenen Suche. Ein Fortschrittsbalken wäre ebenfalls hilfreich, aber nicht zwingend.
3) Ich habe zwei Spalten gemacht. Datei (colDatei) und Ort (colOrt). Ich weis aber nicht, wie ich in der Spalte "Ort" ein Ergebnis anzeigen lassen kann. (erledigt)
4) Momentan werden nur Worddokumente gesucht. Wie man eine ordentliche Verzweigung macht, dass nach den Dokumenten gesucht wird die auch ausgewählt worden sind weis ich noch nicht, aber damit würde ich mich dann erst einmal alleine befassen, sobald die Punkte 1-3 abgedeckt sind
5) Ich würde in der Liste der Ergebnisse gerne ein Dokument öffnen können. Auch damit würde ich mich alleine befassen wollen, wäre über einen kleinen Tipp wonach ich suchen muss dankbar. (contextMenueStrip1 habe ich schon)


Es ist mir nicht wichtig, dass das Tool in perfekt optimierter Programmierung geschrieben wurde. Es soll im wesentlichen funktionieren und (noch wichtiger) nachvollziehbar sein. Daher verzichte ich (vorerst) auf eigene Funktionen. Hier ist der Code, darunter ein Bild der Benutzeroberfläche.

Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace Suche
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            cmbLaufwerk.Items.AddRange(Environment.GetLogicalDrives());
        }

        private void cmdSuchen_Click(object sender, EventArgs e)
        {
            listView1.Items.Clear();
            string path = cmbLaufwerk.Text;
            string searchPattern = "*"+txtSuchbegriff.Text+"*";
            try
            {

                DirectoryInfo di = new DirectoryInfo(path);

                FileInfo[] files = di.GetFiles(searchPattern, SearchOption.AllDirectories);



                foreach (FileInfo file in files)
                {
                    if ((file.Name.Contains(txtSuchbegriff.Text)) && (file.Extension == ".doc"))
                    {
                        listView1.Items.Add(file.Name);
                    }
                }
            }
            catch
            {
                MessageBox.Show("Es ist ein Fehler aufgetreten");
            }
        }

        private void dateiToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void überToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Form2 NeuesFenster = new Form2();
            NeuesFenster.ShowDialog();
        }
    }
}
 

Anhänge

  • SucheGui.png
    SucheGui.png
    14,7 KB · Aufrufe: 15
Zuletzt bearbeitet:
Kleines Update: Ich kann nun auch in Spalte 2 schreiben. Dieser Punkt wäre also erledigt.
Ich würde mich freuen, wenn mir bei den anderen Punkten jemand helfen würde.

Mir kam gerade der Gedanke, ob es sein könnte, dass ich auf lokalen Laufwerken deswegen direkt einen Fehler bekomme, weil es dort Verzeichnisse gibt auf die ich nicht zugreifen darf, beispielsweise der "System Volume Information"-Ordner. Könne das damit zusammen hängen und falls ja, wie kann ich das Problem umgehen?
 
1) Die Suche funktioniert nur auf Netzlaufwerken. Auf den Laufwerken direkt am Computer (also beispielsweise c:\) gibt es einen Fehler. Wieso?
Wie du schon selbst geschrieben hast liegt es ganz einfach daran das dir (dem Programm) die Berechtigungen fehlen.
Das hättest du auch ganz schnell herausgefunden wenn du dir die Exception mal angesehen hättest:
C#:
catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
Um das zu umgehen müsstest du SearchOption.AllDirectories weg lassen und die Ordner einzeln (z.B. rekursiv) durchlaufen.

Es dauert ewig bis ein Ergebnis angezeigt wird. Ich hätte gerne, dass gefundene Dateien direkt angezeigt werden, so wie bei der windowseigenen Suche. Ein Fortschrittsbalken wäre ebenfalls hilfreich, aber nicht zwingend.
Das liegt daran das di.GetFiles() erstmal alles durchsucht und das Ergebnis dann in FileInfo[] files "speichert".
Du zeigst die gefundenen Dateien also erst an wenn alle Dateien gefunden wurden.
Das Problem hättest du auch nicht mehr wenn du Manuell durch jeden Ordner gehen würdest (siehe 1.)
 
OK, der Code sieht jetzt so aus und er findet tatsächlich auch schneller und ohne Fehler auf den lokalen Laufwerken. Danke für den Tipp. Allerdings beschränkt sich die Suche nur auf das Root und schließt keine Unterverzeichnisse mit ein. Dafür werden die Ergebnisse gleich mehrfach ausgegeben.

Code:
        private void cmdSuchen_Click(object sender, EventArgs e)
        {
            listView1.Items.Clear();
            string path = cmbLaufwerk.Text;
            string searchPattern = "*"+txtSuchbegriff.Text+"*";
            try
            {

                DirectoryInfo di = new DirectoryInfo(path);

                 foreach (DirectoryInfo d in di.GetDirectories())
                 {
                     foreach (FileInfo f in di.GetFiles())
                     {
                         if((f.Name.Contains(txtSuchbegriff.Text)) && (f.Extension == ".doc"))
                         {
                             // Spalte 1 (Datei)
                             ListViewItem lvi = new ListViewItem(f.Name);
                             // Spalte 2 (Ort)
                             lvi.SubItems.Add(f.DirectoryName);
                             listView1.Items.Add(lvi);
                         }
                     }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
 

Anhänge

  • SearchMultiResult.png
    SearchMultiResult.png
    18,3 KB · Aufrufe: 10
Allerdings beschränkt sich die Suche nur auf das Root und schließt keine Unterverzeichnisse mit ein
Deswegen mein Hinweis auf Rekursion.

Du wolltest zwar keinen vorgekauten Code aber schau dir folgendes einfach mal an:
C#:
private void cmdSuchen_Click(object sender, EventArgs e)
        {
            listView1.Items.Clear();
            string path = cmdLaufwerk.Text;
            string searchPattern = "*" + txtSuchbegriff.Text + "*";

            System.Threading.Thread t = new System.Threading.Thread(delegate() { Search(path, searchPattern); });
            t.Start();
            
        }

private void Search(string path, string searchPattern)
        {
            try
            {
                UpdateSearchStatus(path);

                DirectoryInfo di = new DirectoryInfo(path);
                FileInfo[] files = di.GetFiles(searchPattern);
                DirectoryInfo[] subDirectories = di.GetDirectories();

                foreach (FileInfo file in files)
                {
                    if ((file.Name.Contains(txtSuchbegriff.Text)) && (file.Extension == ".doc"))
                    {
                        AddFile(file.Name);
                    }
                }

                foreach (DirectoryInfo subDir in subDirectories)
                {
                    Search(subDir.FullName, searchPattern);
                }
            }
            catch (UnauthorizedAccessException) { }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }

        }

private void AddFile(string fileName)
        {
            if (this.listView1.InvokeRequired)
            {
                this.listView1.BeginInvoke(new MethodInvoker(delegate() { AddFile(fileName); }));
            }
            else
            {
                this.listView1.Items.Add(fileName);
            }
        }

        private void UpdateSearchStatus(string path)
        {
            if (this.searchStatus.InvokeRequired)
            {
                this.searchStatus.BeginInvoke(new MethodInvoker(delegate() { UpdateSearchStatus(path); }));
            }
            else
            {
                this.searchStatus.Text = path;
            }
        }

Kurze Erklärung:
- Ich habe die Suche auf einen anderen Thread ausgelagert weil die Suche sonst die Form blockiert.
- Die Search() Methode beginnt im root, sucht nach allen Dateien in diesem Verzeichnis und ruft sich danach für alle Unterverzeichnisse wieder auf. ( Rekursion )
- Ordner die eine UnauthorizedAccessException werfen werden einfach ignoriert ( bzw. die Exception wird ignoriert )
- UpdateSearchStatus status schreibt nur den Pfad des aktuell durchsuchten Ordners in ein Label (searchStatus)
- InvokeRequired und BeginInvoke sind notwendig weil die Suche in einem anderen Thread läuft.
 
Danke rd4eva,

vermutlich könnte ich den Code jetzt 1:1 kopieren und das Grundprinzip des Programms wäre fertig. Ich versuche nun seit gestern immer wieder diesen Code nachzuvollziehen und zwar in dem Sinne, dass ich sage "Logisch, so funktioniert es" und es dann in Zukunft selber machen kann oder auf andere Anwendungen die ich mal programmieren werde übernehmen kann. Das gelingt mir aber nicht, obwohl du netterweise eine kleine Erklärung beigefügt hast. Ich verstehe zwar was jede Zeile macht und warum sie genau so da steht (vom logischen Ablauf her kann ich das alles nachvollziehen), aber wenn ich jetzt daran denke ein ähnliches Programm schreiben zu müssen wo ich das wieder brauche, würde ich es wieder nicht sofort hinbekommen.

Würde es dir etwas ausmachen, den Code so derart simpel zu posten, dass es einfach zu verstehen ist, ohne, dass man abstraktes Denken anwenden muss. Ich muss es erst einmal total simpel sehen, bevor ich es hinbekomme den Code später dann (auch durch weitere Übungen) zu optimieren. Leider liegt mir das so genannte "logische Denken" nicht, weshalb ich es auch bei PHP und MySQL (was ja noch sehr einfache Sprachen sind) so halten muss. Erst einmal einen riesigen Schlauch und dann kann ich beginnen den Code in Funktionen auszulagern oder zu vereinfachen. Zeitraubend aber zumindest bislang die einzige Möglichkeit überhaupt etwas in diesem Bereich machen zu können und zu Hobbyzwecken ohne Zeitdruck ja auch OK. (Irgendwie hoffe ich immer noch, dass es eines Tages "Klick" macht, weil ich mich wirklich für die Programmierung interessiere und sich mir so viele Möglichkeiten eröffnen würden.)

Edit: Mein Problem ist einfach, dass es hier ja um eine ganz einfache Sache geht:
Druchsuche alle Ordner nach Dateien mit Dateiendung *.doc und gebe das Ergebnis aus. Das ist eine total simple Aufgabenstellung und mit PHP/MYSQL in vier oder fünf Zeilen zu lösen (inklusive Ausgabe). Mir will einfach nicht in den Kopf, dass es dafür plötzlich derart viele Zeilen und Befehle braucht. Da blockiert irgendwas in mir.
 
Zuletzt bearbeitet:
Ich hab den Code jetzt mal ausführlich kommentiert, Multithreading weg gelassen und alles in eine Methode gequetscht. Ich hoffe das hilft dir weiter.
C#:
private void cmdSuchen_Click(object sender, EventArgs e)
        {
            listView1.Items.Clear();
            string path = cmdLaufwerk.Text;
            string searchPattern = "*" + txtSuchbegriff.Text + "*";

            //Erster Aufruf der Search Methode
            //path = Das root Verzeichnis in dem die Suche beginnen soll (z.B: C:/)
            //searchPattern = der Suchbegriff (z.B. *meineDatei*)
            Search(path, searchPattern);
        }

        private void Search(string path, string searchPattern)
        {
            //Try catch ist wichtig um eventuelle Exceptions zu fangen
            // vor allem aber um die UnauthorizedAccessException zu fangen
            // von der wir ja wissen das wir sie bekommen werden weil wir auf einige Ordner keine Berechtigungen haben
            try
            {
                DirectoryInfo di = new DirectoryInfo(path);

                //Durchsuche das aktuelle Verzeichnis (und NUR das Verzeichniss und nicht auch noch die Unterordner)
                //nach Dateien auf die der Suchbegriff zutrifft.
                //Beim ersten Aufruf wird also nur C:/ durchsucht.
                FileInfo[] files = di.GetFiles(searchPattern);
                //files enthält jetzt alle Dateien auf die der Suchbegriff passt
                //bzw. ist leer wenn der Suchbegriff auf keine Datei im aktuellen Verzeichniss passt
                               

                //Überprüfe jede Datei im files Array ob der Dateiname den Suchbegriff enthält
                //und ob die Dateieendung = doc ist.
                //BTW: file.Name.Contains(txtSuchbegriff.Text) kannst du weg lassen
                //das ist redundant weil es bereits von di.GetFiles erledigt wurde
                foreach (FileInfo file in files)
                {
                    if ((file.Name.Contains(txtSuchbegriff.Text)) && (file.Extension == ".doc"))
                    {
                        //Wir haben eine Datei gefunden deren Dateiname sowohl den Suchbegriff enthält
                        //als auch .doc als Dateiendung hat
                        // also fügen wir sie der listView hinzu
                        this.listView1.Items.Add(file.Name);
                    }
                }

                //di.GetDirectories gibt alles Unterverzeichnisse des aktuellen Verzeichnisses zurück.
                //Beim ersten Aufruf dann z.B. C:/Programme, C:/Windows
                DirectoryInfo[] subDirectories = di.GetDirectories();
                //subDirectories enthält jetzt alle Unterverzeichnisse des aktuellen Verzeichnisses
                //bzw. ist leer wenn das aktuelle Verzeichnis keine Unterverzeichnisse enthält

                //Für jedes Verzeichniss im subDir Array wird die Methode Search erneut aufgerufen (Rekursion)
                //d.H. der zweite Aufruf würde z.B. so aussehen
                //Search("C:/Programme", "*meineDatei*")
                //Das wird jetzt immer und immer wieder gemacht bis keine Unterverzeichnisse mehr gefunden werden
                foreach (DirectoryInfo subDir in subDirectories)
                {
                    Search(subDir.FullName, searchPattern);
                }
            }
            //An dieser Stelle fangen wir die UnauthorizedAccessException von der wir ja wussten das wir sie
            //bekommen werden weil sie dann geworfen wird wenn wir auf ein Verzeichnis keine Berechtigungen haben
            //Wenn wir also eine solche Exception fangen unternehmen wir garnichts
            catch (UnauthorizedAccessException) { }
            //An dieser Stelle fangen wir alle anderen Exceptions und geben Sie per MessageBox aus            
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }

        }

Das ist eine total simple Aufgabenstellung und mit PHP/MYSQL in vier oder fünf Zeilen zu lösen
Das seh ich ein bischen anders.

Hier hast du das PHP Äquivalent
PHP:
<?php
function Search($path, $searchPattern)
{	
	if ($handle = opendir($path)) 
	{		
		$subDirectories = array();		
		while (false !== ($entry = readdir($handle))) 
		{			
			$fullPath = $path.'\\'.$entry;
			if(is_dir($fullPath) && $entry != '.' && $entry != '..')
			{				
				$subDirectories[] = $fullPath;
				
			}elseif(is_file($fullPath))
			{
				$pathParts = pathinfo($entry);
				if(preg_match('/.*'.$searchPattern.'.*/', $entry) && $pathParts['extension'] == 'doc')
				{
					echo "$fullPath<br />";
				}
			}			
		}		
		closedir($handle);
			
		foreach($subDirectories as $subdir)
		{
			Search($subdir, $searchPattern);
		}
	}
}

Search('C:\\', 'e');
 
Zuletzt bearbeitet:
Danke rd4eva,

ich war die Tage nicht da, deswegen komme ich erst jetzt dazu. Die Erklärung hat mir sehr geholfen. Mit dieser Methode ist die Suche aber "langsam" sehe ich das richtig? Die form wird blockiert und die Ergebnisse erst angezeigt, wenn die Suche fertig ist?

Ich habe etwas herumexperimentiert und frage mich jetzt noch, warum man diesen Part nicht so schreiben könnte:

Code:
            try
            {

                DirectoryInfo di = new DirectoryInfo(path);
                FileInfo[] files = di.GetFiles(searchPattern);

                foreach (FileInfo file in files)
                {
                    DirectoryInfo[] subDirectories = di.GetDirectories();
                    foreach (DirectoryInfo subDir in subDirectories)
                    {
                        Search(subDir.FullName, searchPattern);
                    }
                    if (file.Extension == ".doc")
                    {
                        this.listView1.Items.Add(file.Name);
                    }
                }
            
            }
            catch [...]

Codetechnisch funktioniert es zwar, aber die Suche verirrt sich in den Verzeichniswirren. (Jedenfalls passiert ewig nichts.) Dein Code ist da vergleichsweise sehr schnell. (Dein Code auf C: Einige Sekunden. Meine "Modifikation" Never ending)
 
Mit dieser Methode ist die Suche aber "langsam" sehe ich das richtig?
Prinzipiell sind beide von mir geposteten Varianten gleich schnell.
Das Problem ist nur das du in der 2ten Version das Ergebnis erst angezeigt bekommst wenn alles fertig ist weil kein Multithreading existiert und die Suche somit die Form blockiert.

Ich habe etwas herumexperimentiert und frage mich jetzt noch, warum man diesen Part nicht so schreiben könnte:
...
Codetechnisch funktioniert es zwar...
Weder macht das Sinn noch funktioniert es tatsächlich so wie du es gerne hättest.

Dadurch das du di.GetDirectories(); in die foreach Schleife gepackt hast passiert folgendes (Pseudocode)
Code:
function Suche:
	var directory = aktueller Ordner
	var files = Alle Dateien in directory auf die das Suchmuster passt
	
	für alle Dateien file in files :
		subDirectories = Alle Unterordner von directory
		für alle Ordner dir in subDirectories :
			Beginne Suche erneut in dir
		
		Wenn die Dateiendung von file == doc
			füge file der listview hinzu

Dir sollte auffallen das es jetzt 2 Probleme gibt:
1. Wenn di.GetFiles(searchPattern) kein Ergebnis geliefert hat (d.h. Es wurden im aktuellen Ordner keine Dateien gefunden auf die das Suchmuster passt) dann werden auch keine Unterordner durchsucht
2. Wenn di.GetFiles(searchPattern) ein Ergebnis liefert werden die Unterordner durchsucht. Allerdings werden die Unterordner für jedes Ergebnis in files immer wieder durchsucht.

Also dein Code sucht entweder garnicht, oder viel zu oft.

Oder in anderen Worten:
Das vorhandensein einer gesuchten Datei in einem beliebigen Ordner sagt nicht über das eventuelle vorhandensein weiterer Dateien in Unterordnern des aktuellen Ordners aus. Was logischerweise bedeutet das beide Abläufe nicht aneinander gekoppelt sein dürfen.
 
Zurück