A FileTextBox control in WPF

Have you ever wanted a text file to be displayed in readonly fashion in a WPF window? Perhaps you have a log file and you want the UI to stay synced with the log updates to the file? Maybe you simply want to write a WPF version of WinTail?

Well, I just wrote a control in which you can set file path to a DependencyProperty and the UI will stay up to date. I created a child of TextBox called FileTextBox and implemented FileSystemWatcher.

Update: 6/16/2014 – Added AutoScroll.

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
 
namespace WpfSharp.UserControls
{
    public class FileTextBox : TextBox
    {
        #region Private fields
        private bool _ChangedHandlerAdded;
        private bool _CreatedHandlerAdded;
        private bool _DeletedHandlerAdded;
        private bool _RenameHandlerAdded;
        #endregion
 
        #region constructor
        public FileTextBox()
        {
            AcceptsReturn = true;
            IsReadOnly = true;
            AutoScroll = true;
        }
        #endregion
 
        #region Properties
        /// <summary>
        /// If true, the FileTextBox will always scroll to the end when updated.
        /// </summary>
        public bool AutoScroll { get; set; }
        #endregion
 
        #region File Dependency Property
        public string File
        {
            get { return (string)GetValue(FileProperty); }
            set { SetValue(FileProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for File.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FileProperty =
            DependencyProperty.Register("File", typeof(string), typeof(FileTextBox), new FrameworkPropertyMetadata(OnFilePropertyChanged));
 
        private static void OnFilePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var ftb = sender as FileTextBox;
            if (ftb == null || args.NewValue == null || string.IsNullOrWhiteSpace(args.NewValue.ToString()))
            {
                return;
            }
            var dir = GetDirectory(ref args);
            if (!string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir))
            {
                ftb.Watcher.Path = Path.GetDirectoryName(args.NewValue.ToString());
                ftb.Watcher.Filter = Path.GetFileName(args.NewValue.ToString());
                ftb.AddEvents();
                ftb.Watcher.EnableRaisingEvents = true;
                ftb.UpdateFile();
            }
            else
            {
                ftb.Text = string.Empty;
            }
        }
 
        private static string GetDirectory(ref DependencyPropertyChangedEventArgs args)
        {
            try
            {
                return Path.GetDirectoryName(args.NewValue.ToString());
            }
            catch (Exception)
            {
                return null;
            }
        }
        #endregion
 
        private FileSystemWatcher Watcher
        {
            get { return _Watcher ?? (_Watcher = BuildWatcher()); }
        } private FileSystemWatcher _Watcher;
 
        private FileSystemWatcher BuildWatcher()
        {
            var watcher = new FileSystemWatcher { NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName };
            return watcher;
        }
 
        public void OnFileDeleted(object sender, FileSystemEventArgs e)
        {
            Dispatcher.Invoke(UpdateFile);
        }
 
        public void OnFileChanged(object sender, FileSystemEventArgs e)
        {
            Dispatcher.Invoke(UpdateFile);
        }
 
        public void OnFileCreated(object sender, FileSystemEventArgs e)
        {
            Dispatcher.Invoke(() =>
            {
                UpdateFile();
                EnableRaiseEvents();
            });
        }
 
        public void OnFileRenamed(object sender, RenamedEventArgs e)
        {
            Dispatcher.Invoke(UpdateFile);
        }
 
        private void EnableRaiseEvents()
        {
            Dispatcher.Invoke(() =>
            {
                if (!Watcher.EnableRaisingEvents)
                    Watcher.EnableRaisingEvents = true;
            });
        }
 
        private void UpdateFile()
        {
            if (!System.IO.File.Exists(File))
            {
 
                Text = string.Empty;
                return;
            }
            using (var fs = new FileStream(File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                using (var sr = new StreamReader(fs))
                {
                    Text = sr.ReadToEnd();
                    if (AutoScroll)
                        ScrollToEnd();
                }
            }
        }
 
        private void AddEvents()
        {
            if (!_CreatedHandlerAdded)
            {
                Watcher.Created += OnFileCreated;
                _CreatedHandlerAdded = true;
            }
            if (!_ChangedHandlerAdded)
            {
                Watcher.Changed += OnFileChanged;
                _ChangedHandlerAdded = true;
            }
            if (!_DeletedHandlerAdded)
            {
                Watcher.Deleted += OnFileDeleted;
                _DeletedHandlerAdded = true;
            }
            if (!_RenameHandlerAdded)
            {
                Watcher.Renamed += OnFileRenamed;
                _RenameHandlerAdded = true;
            }
        }
    }
}

As always, feedback is appreciated.

Leave a Reply