Commit d22bdcb

mo <email@solidware.ca>
2011-03-30 23:11:59
add smarter grid.
1 parent e1132f2
product/desktop.ui/model/FederalTaxes.cs
@@ -1,4 +1,6 @@
-namespace solidware.financials.windows.ui.model
+using utility;
+
+namespace solidware.financials.windows.ui.model
 {
     public class FederalTaxes
     {
@@ -7,7 +9,7 @@
             var taxes = 0m;
             if (totalIncome <= 41544.00m)
             {
-                taxes = ((totalIncome - 0m)*0.15m) + 0m;
+                taxes = totalIncome.subtract(0m).multiply_by(0.15m).add(0m);
             }
             if (totalIncome > 41544.00m && totalIncome <= 83088.00m)
             {
product/desktop.ui/model/TaxesForIndividual.cs
@@ -4,7 +4,7 @@ using solidware.financials.windows.ui.presenters;
 
 namespace solidware.financials.windows.ui.model
 {
-    public class TaxesForIndividual : Observable<TaxesForIndividual>
+    public class TaxesForIndividual : ObservablePresenter<TaxesForIndividual>
     {
         public TaxesForIndividual(Guid id, FederalTaxesViewModel federalTaxes)
         {
product/desktop.ui/presenters/ButtonBarPresenter.cs
@@ -9,7 +9,7 @@ using solidware.financials.windows.ui.model;
 
 namespace solidware.financials.windows.ui.presenters
 {
-    public class ButtonBarPresenter : Observable<ButtonBarPresenter>, Presenter, EventSubscriber<AddedNewFamilyMember>
+    public class ButtonBarPresenter : ObservablePresenter<ButtonBarPresenter>, Presenter, EventSubscriber<AddedNewFamilyMember>
     {
         PersonDetails selected_member;
         EventAggregator event_aggregator;
product/desktop.ui/presenters/FederalTaxesViewModel.cs
@@ -1,30 +1,47 @@
 using System;
-using System.Collections.Generic;
 using solidware.financials.windows.ui.model;
+using solidware.financials.windows.ui.views.controls;
 
 namespace solidware.financials.windows.ui.presenters
 {
-    public class FederalTaxesViewModel : Observable<FederalTaxesViewModel>
+    public class FederalTaxesViewModel : ObservablePresenter<FederalTaxesViewModel>
     {
         public FederalTaxesViewModel(Guid id)
         {
             Id = id;
-            FederalTaxesGrid = new List<TaxRow>
-                               {
-                                   new TaxRow {Name = "john doe", Tax = 23456.09m},
-                                   new TaxRow {Name = "sally doe", Tax = 9456.09m},
-                               };
+            FederalTaxesGrid = CreateSampleTable();
         }
 
         public Guid Id { get; private set; }
         public decimal FederalTaxes { get; set; }
         public decimal FederalFamilyTaxes { get; private set; }
-        public IEnumerable<TaxRow> FederalTaxesGrid { get; private set; }
+        public DataGridTable FederalTaxesGrid { get; private set; }
 
         public void ChangeTotalIncomeTo(decimal totalIncome)
         {
             FederalTaxes = new FederalTaxes().CalculateFederalTaxesFor(totalIncome);
+            FederalTaxesGrid.AddRow(x =>
+            {
+                x.AddToCell(new Column<string>("Name"), "blah");
+                x.AddToCell(new Column<decimal>("Tax"), totalIncome);
+            });
             update(x => x.FederalTaxes);
         }
+
+        DataGridTable CreateSampleTable()
+        {
+            var table = new DataGridTable();
+            var nameColumn = table.CreateColumn<string>("Name");
+            var tax = table.CreateColumn<decimal>("Tax");
+
+            table.AddRow(x =>
+            {
+                x.AddToCell(nameColumn, "mo");
+                x.AddToCell(tax, 12345.67m);
+            });
+            table.AddRow(x => x.AddToCell(nameColumn, "allison"));
+            table.FindRowFor(nameColumn, "allison").AddToCell(tax, 98765.43m);
+            return table;
+        }
     }
 }
\ No newline at end of file
product/desktop.ui/presenters/StatusBarPresenter.cs
@@ -4,7 +4,7 @@ using solidware.financials.windows.ui.events;
 
 namespace solidware.financials.windows.ui.presenters
 {
-    public class StatusBarPresenter : Observable<StatusBarPresenter>, Presenter, EventSubscriber<UpdateOnLongRunningProcess>
+    public class StatusBarPresenter : ObservablePresenter<StatusBarPresenter>, Presenter, EventSubscriber<UpdateOnLongRunningProcess>
     {
         public string progress_message { get; set; }
         public bool is_progress_bar_on { get; set; }
product/desktop.ui/presenters/TaxSummaryPresenter.cs
@@ -8,7 +8,7 @@ using solidware.financials.windows.ui.model;
 
 namespace solidware.financials.windows.ui.presenters
 {
-    public class TaxSummaryPresenter : Observable<TaxSummaryPresenter>, TabPresenter, EventSubscriber<IncomeMessage>, EventSubscriber<SelectedFamilyMember>
+    public class TaxSummaryPresenter : ObservablePresenter<TaxSummaryPresenter>, TabPresenter, EventSubscriber<IncomeMessage>, EventSubscriber<SelectedFamilyMember>
     {
         UICommandBuilder builder;
         ServiceBus bus;
product/desktop.ui/views/controls/ClipboardHelper.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Windows;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    static public class ClipboardHelper
+    {
+        static public List<string[]> ParseClipboardData()
+        {
+            var dataObj = Clipboard.GetDataObject();
+            if (dataObj == null) return new List<string[]>();
+
+            var clipboardRawData = dataObj.GetData(DataFormats.Text);
+            if (clipboardRawData == null) return new List<string[]>();
+
+            var rawDataStr = clipboardRawData as string;
+            if (rawDataStr == null && clipboardRawData is MemoryStream)
+            {
+                var ms = clipboardRawData as MemoryStream;
+                var sr = new StreamReader(ms);
+                rawDataStr = sr.ReadToEnd();
+            }
+            Debug.Assert(rawDataStr != null, string.Format("clipboardRawData: {0}, could not be converted to a string or memorystream.", clipboardRawData));
+
+            var clipboardData = new List<string[]>();
+            var rows = rawDataStr.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries);
+            if (rows.Length > 0)
+            {
+                clipboardData = rows.Select(row => ParseTextFormat(row)).ToList();
+            }
+            else
+            {
+                Debug.WriteLine("unable to parse row data.  possibly null or contains zero rows.");
+            }
+
+            return clipboardData.Where(x => x != null && x.Count() > 0 && x.First() != "\0").ToList();
+        }
+
+        static string[] ParseTextFormat(string value)
+        {
+            var outputList = new List<string>();
+            const char separator = '\t';
+            var startIndex = 0;
+            var endIndex = 0;
+
+            for (var i = 0; i < value.Length; i++)
+            {
+                var ch = value[i];
+                if (ch == separator)
+                {
+                    outputList.Add(value.Substring(startIndex, endIndex - startIndex));
+
+                    startIndex = endIndex + 1;
+                    endIndex = startIndex;
+                }
+                else if (i + 1 == value.Length)
+                {
+                    // add the last value
+                    outputList.Add(value.Substring(startIndex));
+                    break;
+                }
+                else
+                {
+                    endIndex++;
+                }
+            }
+
+            return outputList.ToArray();
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/controls/Column.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    public class Column<T> : IEquatable<Column<T>>
+    {
+        public string Title { get; private set; }
+
+        public Column(string title)
+        {
+            Title = title;
+        }
+
+        public override string ToString()
+        {
+            return Title;
+        }
+
+        static public implicit operator Column<T>(string title)
+        {
+            return new Column<T>(title);
+        }
+
+        static public implicit operator string(Column<T> column)
+        {
+            return column.Title;
+        }
+
+        public bool Equals(Column<T> other)
+        {
+            if (ReferenceEquals(null, other)) return false;
+            if (ReferenceEquals(this, other)) return true;
+            return Equals(other.Title, Title);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+            if (obj.GetType() != typeof (Column<T>)) return false;
+            return Equals((Column<T>) obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return (Title != null ? Title.GetHashCode() : 0);
+        }
+
+        public static bool operator ==(Column<T> left, Column<T> right)
+        {
+            return Equals(left, right);
+        }
+
+        public static bool operator !=(Column<T> left, Column<T> right)
+        {
+            return !Equals(left, right);
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/controls/DataGridTable.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq;
+using gorilla.utility;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    public class DataGridTable : SmartCollection<Row>
+    {
+        public virtual Row FindRowFor<ColumnType>(Column<ColumnType> column, ColumnType expectedValue)
+        {
+            return FindRowMatching(row => row.ValueStoredIn(column).Equals(expectedValue));
+        }
+
+        public virtual Row FindRowMatching(Func<Row, bool> condition)
+        {
+            return this.First(condition);
+        }
+
+        public virtual void AddRow(Action<Row> configureRow)
+        {
+            var row = new Row(this);
+            configureRow(row);
+            Add(row);
+        }
+
+        public virtual Column<T> CreateColumn<T>(string columnName)
+        {
+            var column = new Column<T>(columnName);
+            this.each(x => x.AddToCell(column, default(T)));
+            return column;
+        }
+
+        public virtual Column<T> FindColumn<T>(string columnName)
+        {
+            return this.Any() ? null : this.First().FindColumn<T>(columnName);
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/controls/IObservable.cs
@@ -0,0 +1,9 @@
+using System.ComponentModel;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    public interface IObservable : INotifyPropertyChanged
+    {
+        object Value { get; }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/controls/ObjectExtensions.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    static public class ObjectExtensions
+    {
+        static public bool IsNumeric(this object value)
+        {
+            return value.Is<short>() || value.Is<int>() || value.Is<long>() || value.Is<decimal>() || value.Is<float>() || value.Is<double>();
+        }
+
+        static public bool IsNull<T>(this T item) where T : class
+        {
+            return item == null;
+        }
+
+        static public bool Is<T>(this object value)
+        {
+            return value is T;
+        }
+        public static T As<T>(this object value)
+        {
+            if (value is T)
+                return (T)value;
+            if(value is IObservable)
+            {
+                return (T)((IObservable)value).Value;
+            }
+            return (T)Convert.ChangeType(value, typeof (T));
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/controls/Observable.cs
@@ -0,0 +1,39 @@
+using System.ComponentModel;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    public class Observable<T> : IObservable
+    {
+        private T value;
+
+        public Observable() { }
+
+        public Observable(T value)
+        {
+            this.value = value;
+        }
+
+        public T Value
+        {
+            get { return value; }
+            set
+            {
+                this.value = value;
+                PropertyChanged(this, new PropertyChangedEventArgs("Value"));
+            }
+        }
+
+        object IObservable.Value { get { return value; } }
+
+        public static implicit operator T(Observable<T> val)
+        {
+            return val.value;
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged = delegate { };
+        public override string ToString()
+        {
+            return value.ToString();
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/controls/Row.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.utility;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    public class Row : Dictionary<string, IObservable> 
+    {
+        DataGridTable table;
+
+        public Row(DataGridTable table)
+        {
+            this.table = table;
+        }
+
+        public virtual T ValueStoredIn<T>(Column<T> column)
+        {
+            return this[column].As<T>();
+        }
+
+        public virtual void AddToCell<T>(Column<T> column, T value)
+        {
+            AddToCell(column, (IObservable)new Observable<T>(value));
+        }
+
+        public virtual void AddToCell<T>(Column<T> column, IObservable value)
+        {
+            this[column] = value;
+            table.each(row =>
+            {
+                if (!row.HasValueStoredIn(column))
+                    row[column] = new Observable<T>(default(T));
+            });
+        }
+
+        public virtual bool HasValueStoredIn<T>(Column<T> column)
+        {
+            return ContainsKey(column);
+        }
+
+        public virtual Column<T> FindColumn<T>(string name)
+        {
+            return Keys.FirstOrDefault(x => x == name);
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/controls/SmartCollection.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    public class SmartCollection<T> : ObservableCollection<T>
+    {
+        bool suspend;
+
+        public SmartCollection()
+        {
+            suspend = false;
+        }
+
+        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+        {
+            if (!suspend) base.OnCollectionChanged(e);
+        }
+
+        public IDisposable BeginEdit()
+        {
+            return new SuspendNotifications<T>(this);
+        }
+
+        class SuspendNotifications<K> : IDisposable
+        {
+            readonly SmartCollection<K> items;
+
+            public SuspendNotifications(SmartCollection<K> items)
+            {
+                this.items = items;
+                items.suspend = true;
+            }
+
+            public void Dispose()
+            {
+                items.suspend = false;
+                items.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+            }
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/controls/SmartGrid.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Media;
+using gorilla.utility;
+
+namespace solidware.financials.windows.ui.views.controls
+{
+    public class SmartGrid : DataGrid
+    {
+        static SmartGrid()
+        {
+            IsReadOnlyProperty.OverrideMetadata(typeof (SmartGrid), new FrameworkPropertyMetadata((o, e) => ((SmartGrid) o).ConfigureColumns()));
+            ValueConvertersProperty.OverrideMetadata(typeof (SmartGrid), new FrameworkPropertyMetadata((o, e) => ((SmartGrid) o).ConfigureColumns()));
+            CommandManager.RegisterClassCommandBinding(typeof (SmartGrid), new CommandBinding(ApplicationCommands.Paste, (o, e) => ((SmartGrid) o).OnExecutedPaste()));
+        }
+
+        static public readonly DependencyProperty ReadOnlyColumnsProperty = DependencyProperty.Register("ReadOnlyColumns", typeof (IEnumerable<string>), typeof (SmartGrid));
+        static public readonly DependencyProperty HeaderColumnsProperty = DependencyProperty.Register("HeaderColumns", typeof (IEnumerable<string>), typeof (SmartGrid));
+
+        static public readonly DependencyProperty ValueConvertersProperty = DependencyProperty.Register("ValueConverters", typeof (IEnumerable<KeyValuePair<string, IValueConverter>>),
+                                                                                                        typeof (SmartGrid));
+
+        static public readonly DependencyProperty ReadOnlyItemsProperty = DependencyProperty.Register("ReadOnlyItems", typeof (IEnumerable<IDictionary<string, object>>), typeof (SmartGrid));
+
+        static readonly SolidColorBrush HeaderColour = Brushes.LightGray;
+        static readonly SolidColorBrush ReadOnlyColour = Brushes.WhiteSmoke;
+
+        public SmartGrid()
+        {
+            SelectionUnit = DataGridSelectionUnit.CellOrRowHeader;
+            ContextMenu = new ContextMenu
+                          {
+                              Items =
+                                  {
+                                      new MenuItem {Command = ApplicationCommands.Copy},
+                                      new MenuItem {Command = ApplicationCommands.Paste}
+                                  }
+                          };
+        }
+
+        public virtual IEnumerable<IDictionary<string, object>> ReadOnlyItems
+        {
+            get { return (IEnumerable<IDictionary<string, object>>) GetValue(ReadOnlyItemsProperty) ?? Enumerable.Empty<IDictionary<string, object>>(); }
+            set { SetValue(ReadOnlyItemsProperty, value); }
+        }
+
+        public IEnumerable<string> ReadOnlyColumns
+        {
+            get { return (IEnumerable<string>) GetValue(ReadOnlyColumnsProperty) ?? Enumerable.Empty<string>(); }
+            set
+            {
+                SetValue(ReadOnlyColumnsProperty, value);
+                if (Columns != null && Columns.Count > 0)
+                {
+                    ConfigureColumns();
+                }
+            }
+        }
+
+        public IEnumerable<string> HeaderColumns
+        {
+            get { return (IEnumerable<string>) GetValue(HeaderColumnsProperty) ?? Enumerable.Empty<string>(); }
+            set
+            {
+                SetValue(HeaderColumnsProperty, value);
+                if (Columns != null && Columns.Count > 0)
+                {
+                    ConfigureColumns();
+                }
+            }
+        }
+
+        public IEnumerable<KeyValuePair<string, IValueConverter>> ValueConverters
+        {
+            get { return (IEnumerable<KeyValuePair<string, IValueConverter>>) GetValue(ValueConvertersProperty) ?? Enumerable.Empty<KeyValuePair<string, IValueConverter>>(); }
+            set
+            {
+                SetValue(ValueConvertersProperty, value);
+                if (Columns != null && Columns.Count > 0)
+                {
+                    ConfigureColumns();
+                }
+            }
+        }
+
+        void ConfigureColumns()
+        {
+            foreach (var column in Columns)
+            {
+                var header = column.Header.ToString();
+                column.IsReadOnly = IsReadOnly || ReadOnlyColumns.Contains(header);
+
+                Brush background = null;
+                if (HeaderColumns.Contains(header))
+                {
+                    background = HeaderColour;
+                }
+                else if (ReadOnlyColumns.Contains(header))
+                {
+                    background = ReadOnlyColour;
+                }
+                if (background != null)
+                {
+                    if (column.CellStyle == null) column.CellStyle = new Style();
+                    SetPropertyIfNotPresent(column, BackgroundProperty, background);
+                    SetPropertyIfNotPresent(column, ForegroundProperty, Brushes.Black);
+                }
+
+                var textColumn = column as DataGridTextColumn;
+                if (textColumn == null) continue;
+                var binding = textColumn.Binding as Binding;
+                if (binding == null) continue;
+                if (binding.Converter != null) continue; // Converter has already been bound and cannot be changed.
+
+                var valueConverter = ValueConverters.SingleOrDefault(x => x.Key == header).Value;
+
+                if (valueConverter != null)
+                {
+                    binding.Converter = valueConverter;
+                }
+            }
+        }
+
+        static void SetPropertyIfNotPresent(DataGridColumn column, DependencyProperty dependencyProperty, Brush brush)
+        {
+            var existing = column.CellStyle.Setters.OfType<Setter>().FirstOrDefault(x => x.Property == dependencyProperty);
+            if (existing == null)
+            {
+                column.CellStyle.Setters.Add(new Setter(dependencyProperty, brush));
+            }
+        }
+
+        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
+        {
+            base.OnItemsSourceChanged(oldValue, newValue);
+            if (newValue == null) return;
+
+           var enumerator = newValue.GetEnumerator();
+            if (!enumerator.MoveNext()) return;
+
+            var firstRow = enumerator.Current as IDictionary<string, IObservable>;
+
+            if (firstRow == null) return;
+
+            AutoGenerateColumns = false; 
+            Columns.Clear();
+            foreach (var pair in firstRow)
+            {
+                Columns.Add(new ExtendedTextColumn {Header = pair.Key, Binding = new Binding("[" + pair.Key + "].Value")});
+            }
+            ConfigureColumns();
+        }
+
+        protected override void OnLoadingRow(DataGridRowEventArgs e)
+        {
+            if (ReadOnlyItems != null)
+            {
+                e.Row.IsEnabled = !IsItemReadOnly(e.Row.Item);
+                if (!e.Row.IsEnabled)
+                {
+                    e.Row.Background = HeaderColour;
+                }
+            }
+
+            base.OnLoadingRow(e);
+        }
+
+        protected virtual void OnExecutedPaste()
+        {
+            OnBeginningEdit(new DataGridBeginningEditEventArgs(Columns.First(), new DataGridRow(), new RoutedEventArgs()));
+            var rowData = ClipboardHelper.ParseClipboardData();
+
+            var minColumnDisplayIndex = SelectedCells.Min(x => x.Column.DisplayIndex);
+            var maxColumnDisplayIndex = SelectedCells.Max(x => x.Column.DisplayIndex);
+            var minRowIndex = SelectedCells.Min(y => Items.IndexOf(y.Item));
+            var maxRowIndex = SelectedCells.Max(y => Items.IndexOf(y.Item));
+
+            // If single cell select, then use as a starting cell rather than limiting the paste
+            if (minColumnDisplayIndex == maxColumnDisplayIndex && minRowIndex == maxRowIndex)
+            {
+                maxColumnDisplayIndex = Columns.Count - 1;
+                maxRowIndex = Items.Count - 1;
+            }
+
+            var rowDataIndex = 0;
+            for (var i = minRowIndex; i <= maxRowIndex && rowDataIndex <= rowData.Count() - 1; i++, rowDataIndex++)
+            {
+                var columnDataIndex = 0;
+                for (var j = minColumnDisplayIndex; j <= maxColumnDisplayIndex && columnDataIndex <= rowData[rowDataIndex].Length - 1; j++, columnDataIndex++)
+                {
+                    var column = ColumnFromDisplayIndex(j);
+                    if (column.IsReadOnly) continue;
+                    if (IsItemReadOnly(Items[i])) continue;
+                    column.OnPastingCellClipboardContent(Items[i], rowData[rowDataIndex][columnDataIndex]);
+                }
+            }
+        }
+
+        bool IsItemReadOnly(object item)
+        {
+            return ReadOnlyItems.Any(x => ReferenceEquals(x, item));
+        }
+
+        protected override void OnKeyUp(KeyEventArgs e)
+        {
+            switch (e.Key)
+            {
+                case Key.Delete:
+                    foreach (var cell in SelectedCells.Where(x => !x.Column.IsReadOnly))
+                    {
+                        // N.B. Passing in an integer value of zero results in the Gas (MCF) column updating,
+                        //      but no other column updating. Using a string "0" results in all values
+                        //      updating properly. Very odd behaviour, but insufficient time to investigate why.
+                        cell.Column.OnPastingCellClipboardContent(cell.Item, "0");
+                    }
+                    OnBeginningEdit(new DataGridBeginningEditEventArgs(Columns.First(), new DataGridRow(), new RoutedEventArgs()));
+                    break;
+                case Key.Enter:
+                    OnBeginningEdit(new DataGridBeginningEditEventArgs(Columns.First(), new DataGridRow(), new RoutedEventArgs()));
+                    break;
+                default:
+                    base.OnKeyUp(e);
+                    break;
+            }
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/ExtendedTextColumn.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace solidware.financials.windows.ui.views
+{
+    public class ExtendedTextColumn : DataGridTextColumn
+    {
+        public HorizontalAlignment HorizontalAlignment
+        {
+            get { return (HorizontalAlignment)GetValue(HorizontalAlignmentProperty); }
+            set { SetValue(HorizontalAlignmentProperty, value); }
+        }
+
+        public static readonly DependencyProperty HorizontalAlignmentProperty =
+            DependencyProperty.Register(
+                "HorizontalAlignment",
+                typeof(HorizontalAlignment),
+                typeof(ExtendedTextColumn),
+                new UIPropertyMetadata(HorizontalAlignment.Stretch));
+
+        public VerticalAlignment VerticalAlignment
+        {
+            get { return (VerticalAlignment)GetValue(VerticalAlignmentProperty); }
+            set { SetValue(VerticalAlignmentProperty, value); }
+        }
+
+        public static readonly DependencyProperty VerticalAlignmentProperty =
+            DependencyProperty.Register(
+                "VerticalAlignment",
+                typeof(VerticalAlignment),
+                typeof(ExtendedTextColumn),
+                new UIPropertyMetadata(VerticalAlignment.Stretch));
+
+        private TextAlignment GetTextAlignment()
+        {
+            switch (HorizontalAlignment)
+            {
+                case HorizontalAlignment.Center:
+                    return TextAlignment.Center;
+                case HorizontalAlignment.Left:
+                    return TextAlignment.Left;
+                case HorizontalAlignment.Right:
+                    return TextAlignment.Right;
+                case HorizontalAlignment.Stretch:
+                    return TextAlignment.Justify;
+                default:
+                    throw new ArgumentOutOfRangeException("HorizontalAlignment", "Unsupported alignment type!");
+            }
+        }
+
+        protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
+        {
+            var element = base.GenerateElement(cell, dataItem);
+
+            element.HorizontalAlignment = HorizontalAlignment;
+            element.VerticalAlignment = VerticalAlignment;
+
+            return element;
+        }
+
+        protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
+        {
+            var textBox = (TextBox)base.GenerateEditingElement(cell, dataItem);
+
+            textBox.TextAlignment = GetTextAlignment();
+            textBox.VerticalContentAlignment = VerticalAlignment;
+
+            return textBox;
+        }
+    }
+}
\ No newline at end of file
product/desktop.ui/views/TaxSummaryTab.xaml
@@ -2,8 +2,7 @@
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-             mc:Ignorable="d" >
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:controls="clr-namespace:solidware.financials.windows.ui.views.controls" mc:Ignorable="d" >
     <DockPanel MinWidth="1024">
         <StackPanel>
             <StackPanel Margin="5,5,5,5">
@@ -18,7 +17,7 @@
             </DockPanel>
             </StackPanel>
             <DockPanel LastChildFill="False" HorizontalAlignment="Stretch">
-            <StackPanel Margin="5,5,5,5" Width="500">
+            <StackPanel Margin="5,5,5,5" Width="500" DataContext="{Binding Path=FederalTaxes}">
                 <Label FontWeight="Bold">Federal Taxes</Label>
                 <Expander Header="Tax Rates">
                 <StackPanel>
@@ -33,13 +32,13 @@
                 </Expander>
                 <DockPanel>
                     <Label Width="100">Individual:</Label>
-                    <Label Content="{Binding Path=FederalTaxes.FederalTaxes}" FontWeight="Bold"/>
+                    <Label Content="{Binding Path=FederalTaxes}" FontWeight="Bold"/>
                 </DockPanel>
                 <DockPanel>
                     <Label Width="100">Family:</Label>
-                    <Label Content="{Binding Path=FederalTaxes.FederalFamilyTaxes}" FontWeight="Bold"/>
+                    <Label Content="{Binding Path=FederalFamilyTaxes}" FontWeight="Bold"/>
                 </DockPanel>
-                <DataGrid Margin="5,5,5,5" ItemsSource="{Binding FederalTaxes.FederalTaxesGrid, Mode=OneWay}" IsReadOnly="True"></DataGrid>
+                <controls:SmartGrid Margin="5,5,5,5" ItemsSource="{Binding FederalTaxesGrid, Mode=OneWay}" IsReadOnly="True"></controls:SmartGrid>
             </StackPanel>
             <StackPanel Margin="5,5,5,5" Width="500">
                 <Label FontWeight="Bold">Provincial Taxes</Label>
product/desktop.ui/views/TaxSummaryTab.xaml.cs
@@ -8,5 +8,10 @@ namespace solidware.financials.windows.ui.views
         {
             InitializeComponent();
         }
+
+        public void bind_to(TaxSummaryPresenter presenter)
+        {
+            DataContext = presenter;
+        }
     }
 }
\ No newline at end of file
product/desktop.ui/Observable.cs → product/desktop.ui/ObservablePresenter.cs
@@ -5,7 +5,7 @@ using gorilla.utility;
 
 namespace solidware.financials.windows.ui
 {
-    public abstract class Observable<T> : INotifyPropertyChanged
+    public abstract class ObservablePresenter<T> : INotifyPropertyChanged
     {
         public event PropertyChangedEventHandler PropertyChanged = (o, e) => { };
 
product/desktop.ui/solidware.financials.csproj
@@ -126,7 +126,7 @@
     <Compile Include="model\FederalTaxes.cs" />
     <Compile Include="model\TaxesForIndividual.cs" />
     <Compile Include="model\TaxRow.cs" />
-    <Compile Include="Observable.cs" />
+    <Compile Include="ObservablePresenter.cs" />
     <Compile Include="Presenter.cs" />
     <Compile Include="PresenterFactory.cs" />
     <Compile Include="presenters\AddFamilyMemberPresenter.cs" />
@@ -157,12 +157,21 @@
       <DependentUpon>AddNewIncomeDialog.xaml</DependentUpon>
     </Compile>
     <Compile Include="views\ButtonExpression.cs" />
+    <Compile Include="views\controls\ClipboardHelper.cs" />
+    <Compile Include="views\controls\Column.cs" />
+    <Compile Include="views\controls\DataGridTable.cs" />
+    <Compile Include="views\controls\IObservable.cs" />
+    <Compile Include="views\controls\ObjectExtensions.cs" />
+    <Compile Include="views\controls\Observable.cs" />
+    <Compile Include="views\controls\Row.cs" />
+    <Compile Include="views\controls\SmartCollection.cs" />
     <Compile Include="views\DisplayCanadianTaxInformationDialog.xaml.cs">
       <DependentUpon>DisplayCanadianTaxInformationDialog.xaml</DependentUpon>
     </Compile>
     <Compile Include="views\ErrorWindow.xaml.cs">
       <DependentUpon>ErrorWindow.xaml</DependentUpon>
     </Compile>
+    <Compile Include="views\ExtendedTextColumn.cs" />
     <Compile Include="views\icons\UIIcon.cs" />
     <Compile Include="views\icons\IconMarker.cs" />
     <Compile Include="views\ImageButton.cs" />
@@ -177,6 +186,7 @@
     <Compile Include="views\ShellWIndow.xaml.cs">
       <DependentUpon>ShellWIndow.xaml</DependentUpon>
     </Compile>
+    <Compile Include="views\controls\SmartGrid.cs" />
     <Compile Include="views\StatusBarRegion.xaml.cs">
       <DependentUpon>StatusBarRegion.xaml</DependentUpon>
     </Compile>
product/desktop.ui/Tab.cs
@@ -1,4 +1,7 @@
 namespace solidware.financials.windows.ui
 {
-    public interface Tab<Presenter> : View<Presenter> where Presenter : TabPresenter {}
+    public interface Tab<Presenter> : View<Presenter> where Presenter : TabPresenter
+    {
+        void bind_to(Presenter presenter);
+    }
 }
\ No newline at end of file
product/desktop.ui/WPFApplicationController.cs
@@ -19,19 +19,19 @@ namespace solidware.financials.windows.ui
             this.factory = factory;
         }
 
-        public void add_tab<Presenter, View>() where Presenter : TabPresenter
-            where View : FrameworkElement, Tab<Presenter>, new()
+        public void add_tab<Presenter, View>() where Presenter : TabPresenter where View : FrameworkElement, Tab<Presenter>, new()
         {
             var presenter = open<Presenter>();
+            var view = new View();
+            view.bind_to(presenter);
             configure_region<DocumentPane>(x => x.Items.Add(new DocumentContent
             {
                 Title = presenter.Header,
-                Content = new View {DataContext = presenter},
+                Content = view,
             }));
         }
 
-        public void load_region<TPresenter, Region>() where TPresenter : Presenter
-            where Region : FrameworkElement, View<TPresenter>, new()
+        public void load_region<TPresenter, Region>() where TPresenter : Presenter where Region : FrameworkElement, View<TPresenter>, new()
         {
             configure_region<Region>(x => { x.DataContext = open<TPresenter>(); });
         }
product/utility/Mathematics.cs
@@ -0,0 +1,25 @@
+namespace utility
+{
+    static public class Mathematics
+    {
+        static public decimal subtract(this decimal right, decimal left)
+        {
+            return right - left;
+        }
+
+        static public decimal add(this decimal right, decimal left)
+        {
+            return right + left;
+        }
+
+        static public decimal multiply_by(this decimal right, decimal left)
+        {
+            return right*left;
+        }
+
+        static public decimal divided_by(this decimal right, decimal left)
+        {
+            return right/left;
+        }
+    }
+}
\ No newline at end of file
product/utility/utility.csproj
@@ -45,6 +45,7 @@
   <ItemGroup>
     <Compile Include="MapKey.cs" />
     <Compile Include="MapperRegistery.cs" />
+    <Compile Include="Mathematics.cs" />
     <Compile Include="NeedsShutdown.cs" />
     <Compile Include="NeedStartup.cs" />
   </ItemGroup>