1

Here is the problem using WPF and Datagrid.

There are a many object that users want to see as rows in table, where columns are dates.

|  Name     |Jan 2014|Feb 2014|Mar 2014|...|
--------------------------------------------
| coolObj1  |    10.0|    10.0|    20.0|...|
| coolObj2  |    15.0|    19.0|    25.0|...|

My questions:

  • These code works, but have no ideas of how to correctly implement validation. What if values will not be only doubles (that can be int's or strings too)?
  • What if users want to restrict some values for range [0..50]?
  • Is there way to create that columns with XAML?
  • Maybe there are more suitable ways to represent such "many-columns" data ?

In Qt there were a lot of ways of working with situations like this (use QAbstractItemModel Luke!)

Screenshot of result

XAML Code:

<Window x:Class="Columns.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Find easter eggs" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button Grid.Row="0" HorizontalAlignment="Left" Margin="5" Click="OnClick">Update grid</Button>
        <DataGrid x:Name="Grid"
                  CanUserAddRows="False"
                  CanUserDeleteRows="False"
                  CanUserResizeRows="False"
                  CanUserReorderColumns="False"
                  CanUserResizeColumns="True"
                  CanUserSortColumns="False"
                  Grid.Row="1" />
    </Grid>
</Window>

Xaml.cs code:

namespace Columns
{
    public partial class MainWindow
    {
        private SomeDict _dict;

        public MainWindow()
        {
            InitializeComponent();
            GetData();
            UpdateView();
        }

        private void GetData()
        {
            // We assume that there can be a lot of dicts
            // Currently we work with the only one

            _dict = new SomeDict();
            Random rnd = new Random();

            var start = new DateTime(2010, 1, 1);
            var end = new DateTime(2020, 1, 1);

            for (DateTime date = start; date < end; date = date.AddMonths(1))
            {
                _dict.Add(date, rnd.NextDouble());
            }
        }

        private void UpdateView()
        {
            var row = new RowContext();

            // Create name column
            string columnName = "Name";
            ICellContext cellContext = new NameCell("Easter egg");
            row[columnName] = cellContext;

            Grid.Columns.Clear();
            Grid.Columns.Add(new DataGridTextColumn()
            {
                Binding = new Binding(columnName),
                Header = columnName,
                Width = 100,
            });

            var stringToDoubleConverter = new StringToDoubleConverter();

            // Create column for each date in dictionary

            foreach (var pair in _dict)
            {
                DateTime date = pair.Key;
                columnName = AsColumnName(date);

                Grid.Columns.Add(new DataGridTextColumn()
                {
                    Binding = new Binding(columnName)
                    {
                        StringFormat = "N3",
                        Mode = BindingMode.TwoWay,
                        Converter = stringToDoubleConverter,
                    },
                    IsReadOnly = false,
                    Width = 80,
                    Header = AsReadable(date),
                });

                cellContext = new CellContext(_dict, date, typeof (double), pair.Value);
                row[columnName] = cellContext;
            }

            // Finally set ItemsSource
            Grid.ItemsSource = new[] {row};
        }

        private string AsReadable(DateTime date)
        {
            return date.ToString("d");
        }

        private string AsColumnName(DateTime date)
        {
            return date.ToString("yy-MM-dd");
        }

        private void OnClick(object sender, RoutedEventArgs e)
        {
            UpdateView();
        }
    }

    public interface ICellContext
    {
        object Value { get; set; }
        bool IsEditable { get; }
        Type PropertyType { get; }
    }

    internal sealed class NameCell : ICellContext
    {
        private string _name;

        public NameCell(string name)
        {
            _name = name;
        }

        public object Value
        {
            get { return _name; }
            set { }
        }

        public bool IsEditable
        {
            get { return false; }
        }

        public Type PropertyType
        {
            get { return typeof(string); }
        }
    }

    internal sealed class CellContext : ICellContext, INotifyPropertyChanged
    {
        private readonly bool _isEditable;
        private readonly SomeDict _dict;
        private readonly DateTime _date;
        private readonly Type _propertyType;
        private object _initialValue;

        /// <summary>
        ///     Initializes a new instance of the <see cref="T:System.Object" /> class.
        /// </summary>
        public CellContext(SomeDict dict, DateTime date, Type propertyType, object initialValue, bool isEditable=true)
        {
            _dict = dict;
            _date = date;
            _propertyType = propertyType;
            _initialValue = initialValue;
            _isEditable = isEditable;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public object Value
        {
            get { return _initialValue; }
            set
            {
                Debug.Assert(value.GetType() == PropertyType);
                if (Equals(value, _initialValue)) return;

                // Create do/undo action, write value
                _dict[_date] = Convert.ToDouble(value);
                _initialValue = value;

                OnPropertyChanged("Value");
            }
        }

        public bool IsEditable
        {
            get { return _isEditable; }
        }

        public Type PropertyType
        {
            get { return _propertyType; }
        }

        [NotifyPropertyChangedInvocator]
        private void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public sealed class RowContext : DynamicObject, INotifyPropertyChanged
    {
        private readonly IDictionary<string, object> data;

        public RowContext()
        {
            data = new Dictionary<string, object>();
        }

        public object this[string columnName]
        {
            get
            {
                object value;
                if (data.TryGetValue(columnName, out value))
                {
                    var iproperty = value as ICellContext;
                    if (iproperty != null)
                    {
                        return iproperty.Value;
                    }

                    return value;
                }

                return null;
            }
            set
            {
                object value1;
                if (!data.TryGetValue(columnName, out value1))
                {
                    data.Add(columnName, value);
                    OnPropertyChanged(columnName);
                }
                else
                {
                    var iproperty = value1 as ICellContext;
                    if (iproperty != null)
                    {
                        iproperty.Value = value;
                        OnPropertyChanged(columnName);
                    }
                    else if (value1 != value)
                    {
                        data[columnName] = value;
                        OnPropertyChanged(columnName);
                    }
                }
            }
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return data.Keys;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = this[binder.Name];
            return true;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            this[binder.Name] = value;
            return true;
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }

    internal sealed class SomeDict : Dictionary<DateTime, object>
    {
    }
}
5
  • 1
    Yes, you can do it in XAML if everything is pre-defined. Validation is a whole other animal though. Could you narrow the question at all? Commented Sep 8, 2014 at 18:20
  • @Rustam explain to me what you need chat.stackoverflow.com/rooms/18165/wpf Commented Sep 8, 2014 at 19:18
  • @BradleyDotNET, I know that if all of columns are known (e. g. properties of some object), there will be a way to define that in XAML. But what if dates range is changeable? Commented Sep 9, 2014 at 0:58
  • I'm not sure what you mean. Why would the range of a date column change the XAML? Commented Sep 9, 2014 at 2:46
  • Can u implement the example above using only XAML? Commented Sep 9, 2014 at 2:56

1 Answer 1

1

It seems that there only two ways for implement such behaviour:

  1. By using DynamicObject as in example above.
  2. By using DataTable
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.