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.
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.