Entwickler-Ecke

WinForms - Datei-Inhalte live anzeigen (und Inhalte aktuell halten)


rittergig - Di 23.08.11 14:25
Titel: Datei-Inhalte live anzeigen (und Inhalte aktuell halten)
Ich möchte den Inhalt einer Datei in einer Textbox in Echtzeit anzeigen.
Wenn die Anwendung gestartet ist, soll sie immer den aktuellen Inhalt einer Datei anzeigen.
Also wenn ich die Datei mit einem Text-Editor öffne und was hinzufüge und dann wieder abspeicher soll das auch im Programm angezeigt werden.

Wie kann ich das realisieren außer ständig zu pollen.
Hat der StreamReader irgendein Event für aktualisierung oder kann ich an ein Textfeldcontrol direkt den Stream als Datasource übergeben?

Grüße, Peter


lothi - Di 23.08.11 14:35

Hallo

Ja, dafür gibs die FileSystemWatcher - Klasse

Gruss Lothi


rittergig - Di 23.08.11 21:41

user profile iconlothi hat folgendes geschrieben Zum zitierten Posting springen:
Ja, dafür gibs die FileSystemWatcher - Klasse


Danke. Damit gehts.

Allerdings habe ich noch das Problem, dass das Changed-Ereignis beim Speichern einer Datei 2 mal (oder noch öfter) ausgelöst wird.
Ich habe die Eigenschaft NotifyFilter = NotifyFilters.Attributes festgelegt.
Dennoch wird der Event-Handler "_FileSystemWatcher_Changed" 2x aufgerufen.

Leider kann ich auch nicht den 2. Aufruf anhand des FileSystemEventArgs filtern, da diese Argumente völlig identisch sind.

Wie kann ich das Problem beheben?

Grüße Peter


lothi - Do 25.08.11 12:28

Hallo

Hmm so vom Ufer aus kann ich auch nicht sagen wieso. Setz mal ein Breakpoint und fahre mit dem Debugger durch.
Vielleicht siehst du wo und wann das Event gefeurt wird.

Gruss Lothi


rittergig - Do 25.08.11 19:51

Die Events werden nahezu parallel gefeuert. Das merke ich daran, dass ich, sobald sich eine Datei ändert, ich die Datei anzeigen möchte.
Oft erhalte ich aber eine Exception, weil bereits ein StreamReader die Datei geöffnet hat.

Hier mal mein Quellcode:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
public partial class Form1 : Form
    {
        #region Fiels
        private FileInfo _OpenedFile;
        private FileSystemWatcher _FileSystemWatcher;
        #endregion

        #region Constructors
        public Form1()
        {
            InitializeComponent();
        }
        #endregion

        #region Private Methods
        private void ShowFile(string filePath)
        {
            InvokeIfRequired(this.textBoxFileContent, (MethodInvoker)delegate()
            {
                for (int i = 0; i < 5; i++)
                {
                    try
                    {
                        using (StreamReader sr = new StreamReader(filePath))
                        {
                            this.textBoxFileContent.Text = sr.ReadToEnd();
                            sr.Close();
                        }
                        break;
                    }
                    catch (IOException)
                    {
                        System.Threading.Thread.Sleep(50);
                    }
                }
            });
        }

        // Hilfsfunktion: Controls aktualisieren, auch aus anderen Thread heraus
        private void InvokeIfRequired(Control target, Delegate methodToInvoke)
        {
            /* Mit Hilfe von InvokeRequired wird geprüft ob der Aufruf direkt an die UI gehen kann oder
             * ob ein Invokeing hier von Nöten ist
             */

            if (target.InvokeRequired)
            {
                // Das Control muss per Invoke geändert werden, weil der aufruf aus einem Backgroundthread kommt
                target.Invoke(methodToInvoke);
            }
            else
            {
                // Die Änderung an der UI kann direkt aufgerufen werden.
                methodToInvoke.DynamicInvoke();
            }
        }
        #endregion

        private void buttonOpenFile_Click(object sender, EventArgs e)
        {
            string filePath = textBoxFilePath.Text;

            if (File.Exists(filePath) == true)
            {
                this._OpenedFile = new FileInfo(filePath);

                this._FileSystemWatcher = new FileSystemWatcher()
                {
                    Path = this._OpenedFile.DirectoryName,
                    Filter = this._OpenedFile.Name,
                    NotifyFilter = NotifyFilters.Attributes,
                    IncludeSubdirectories = false,
                    EnableRaisingEvents = true,
                };
                this._FileSystemWatcher.Changed += new FileSystemEventHandler(this._FileSystemWatcher_Changed);

                this.ShowFile(this._OpenedFile.FullName);
            }
        }

        void _FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            // wird 2x gleichzeitig aufgerufen
            this.ShowFile(this._OpenedFile.FullName);
        }
    }


Wenn ich auf den Button klicke, wird die Datei angezeigt.
Bei Änderung soll die Anzeige aktualisiert werden.

Problem ist nur, dass das Event 2x gefeuert wird.

Mit
NotifyFilter = NotifyFilters.Size
wird das Event nur 1x gefeuert.
Aber ich bekomme dann nicht mit, wenn sich die Datei ändert, jedoch die Größe konstant bleibt.

Grüße Peter


Trashkid2000 - Do 25.08.11 21:53

Hallo Peter,

also nachdem ich das auf stackoverflow (http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice) gelesen habe ist es wohl das beste, wenn man sich im Prozess einfach das letzte Änderungsdatum merkt. Und dann im Event mit diesem vergleicht. Das würde dann so aussehen:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
private void button1_Click(object sender, EventArgs e)
{
  string filePath = @"c:\temp\testfile.txt";
  if (File.Exists(filePath))
  {
    DateTime lastFileChangedTime = File.GetLastWriteTime(filePath);
    FileSystemWatcher watcher = new FileSystemWatcher()
    {
      Path = Path.GetDirectoryName(filePath),
      Filter = Path.GetFileName(filePath),
      NotifyFilter = NotifyFilters.Attributes,
      IncludeSubdirectories = false,
      EnableRaisingEvents = true,
    };
    watcher.Changed += (x, y) =>
    {
      if (File.GetLastWriteTime(filePath) != lastFileChangedTime)
      {
        lastFileChangedTime = File.GetLastWriteTime(filePath);
        
        //do something
      }
    };
  }
}

LG,


rittergig - Fr 26.08.11 09:02

user profile iconTrashkid2000 hat folgendes geschrieben Zum zitierten Posting springen:
also nachdem ich das auf stackoverflow (http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice) gelesen habe ist es wohl das beste, wenn man sich im Prozess einfach das letzte Änderungsdatum merkt. Und dann im Event mit diesem vergleicht.


Danke. Aber schade, dass es nicht out-of-the-box richtig funktioniert.

Das Problem bei dieser Lösung ist, dass die Befehle nicht in einer Transaktion gekapselt sind. Es könnte ja sein, dass bevor die LastWrite-Zeit auslesen wird, die Datei bereits erneut geändert wurde. Evtl. müsste man sogar mit Semaphoren arbeiten.
Aber erst einmal funktioniert der Hack :)