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.

Leave a Reply