0

I'm trying to bind data to some sort of custom dependency property or attached property in WPF. I've used the following question and answer- How to create Custom Property for WPF DataGridTextColumn as a frame of reference but I need to bind to the 'tag' in WPF. We can call it whatever, but I call it a tag for simplicity as I'm using it on some checkboxes and textboxes in other parts of the code. My Class is as follows:

public class TagTextColumns : DependencyObject
{
    public static readonly DependencyProperty TagProperty = DependencyProperty.RegisterAttached(
        "Tag",
        typeof(object),
        typeof(DataGridColumn),
        new FrameworkPropertyMetadata(null));

    public static object GetTag(DependencyObject dependencyObject)
    {
        return dependencyObject.GetValue(TagProperty);
    }

    public static void SetTag(DependencyObject dependencyObject, object value)
    {
        dependencyObject.SetValue(TagProperty, value);
    }
}

I'd like to setup my DataGridTextColumn in WPF to something similar to the following:

<DataGridTextColumn Binding="{Binding Customer}" Header="Customer" local:TagTextColumns.Tag="{Binding tkey}"/>

where tkey is a reference in the db. I do all this to calculate subtotals in the last column per row. Right now, I use a DataGridTemplateColumn with a TextBox inside so I can Tag it with tkey I've also read up on TechNet about these things but there seems to be little pointing to what exactly I want to do here. I'm not even sure it's possible, but it would seem like it would be.

EDIT:

This is the code I'm using to attempt to call the tag on CellEditEndingfiring

private void resultsDg_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        MessageBox.Show(TagTextColumns.GetTag(sender as DataGridTextColumn).ToString());
    }

More details about the problem at hand: I am trying on four fields from the database to dynamically calculate the subtotal for the last column as the user makes changes. I was trying to use Linq to SQL for this, but using System.Data.SqlClient and System.Data was much faster. Maybe someone can show me a better way. The calculation code is below:

    private void CalculateAscessorials(string tkey)
    {
        decimal SplitTerm = Properties.Settings.Default.SplitTerminal;
        decimal SplitDrop = Properties.Settings.Default.SplitDrop;
        decimal SiteSplit = Properties.Settings.Default.OnSiteSplit;
        decimal Trainer = Properties.Settings.Default.TrainerLoad;
        decimal WaitTime = Properties.Settings.Default.TerminalWait;
        decimal PumpOut = Properties.Settings.Default.PumpOut;

        DataRow[] row = resultDetail.Select("tkey = " + tkey);

        decimal payRate;
        decimal tempLineItem;
        Decimal.TryParse(row[0]["PayForLine"].ToString(), out tempLineItem);
        Decimal.TryParse(row[0]["PayRate"].ToString(), out payRate);
        if (payRate == 0 && tempLineItem > 0) //this change affts if the rate is 0, that is no rate defined, the rate entered becomes the pay rate for that line only for calculations
            row[0]["PayRate"] = tempLineItem;
        if (!Convert.ToBoolean(row[0]["SplitDrop"]))
        {
            Decimal.TryParse(row[0]["PayRate"].ToString(), out payRate); 
        }
        else if (Convert.ToBoolean(row[0]["SplitDrop"]))
        {
            payRate = SplitDrop;
        }
        //decimal linePay;
        //    Decimal.TryParse(row[0]["PayForLine"].ToString(), out linePay);
        int terms;
        Int32.TryParse(row[0]["SplitLoad"].ToString(), out terms);
        decimal waits;
        Decimal.TryParse(row[0]["WaitTime"].ToString(), out waits);
        int pumps;
        Int32.TryParse(row[0]["PumpOut"].ToString(), out pumps);
        int sites;
        Int32.TryParse(row[0]["SiteSplit"].ToString(), out sites);
        int trainings;
        Int32.TryParse(row[0]["Trainer"].ToString(), out trainings);
        row[0]["PayForLine"] =
                    (SplitTerm * terms)
                    + (waits * WaitTime)
                    + (pumps * PumpOut)
                    + (sites * SiteSplit)
                    + (trainings * Trainer)
                    + payRate;
    }
Community
  • 1
  • 1
  • What's the problem exactly? – redcurry Nov 14 '16 at 03:09
  • Tell us what you want to achieve. Looks like you just want to have a additional subtotal column. http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – Mat Nov 14 '16 at 08:17
  • In the RegisterAttached method change `typeof(DataGridColumn)` to `typeof(TagTextColumns)`. Also no need to derive from `DependencyObject` for [Attached Properties](http://stackoverflow.com/a/11535994/4838058). – Funk Nov 14 '16 at 09:01
  • @redcurry the problem is whenever `CellEditEnding` fires, I'm getting a null for my tag value. This is what I'm attempting right now. All I want to see for the moment is the tag, then I can add the rest: `MessageBox.Show(TagTextColumns.GetTag(sender as DataGridTextColumn).ToString());` – Jeremiah Halstead Nov 14 '16 at 13:03
  • @Funk I had tried `typeof(TagTextColumns)` and was playing around with other things trying to understand what exactly what was going on and how to for this to work. That was also what I was trying with the Derived Class as well. I had seen that post before, which is what set me off in this direction. – Jeremiah Halstead Nov 14 '16 at 13:10
  • @mat I will add some additional details, I am trying to subtotal dynamically. I solved it previously for another window by adding an edit button to the row and doing a `DialogBox` to force the user to edit there. – Jeremiah Halstead Nov 14 '16 at 13:13
  • I think your users will get crazy if you throw a message box on them any time they edited a cell. I also think the way how you try to solve the "subtotal" is a little bit strange. Did you have a look on this example already? http://stackoverflow.com/questions/37284194/calculated-column-in-datagrid-wpf You really should calculate the fields on the ViewModel. – Mat Nov 14 '16 at 13:23
  • @mat That dialog bog is just for my reference while testing to see if it works and gives me the data I want. The ultimate plan is to use that data for my calculation as shown in the latest edit above – Jeremiah Halstead Nov 14 '16 at 14:00
  • I would really try to go for MVVM and not parse all values "win forms style". I try to post a example ASAP – Mat Nov 14 '16 at 14:06

1 Answers1

0

You can use the MVVM pattern to calculate the subtotals in the ViewModel. Here's a example that should lead you to the right direction. In the constructor of the MainView I create 2 courses and apply students. A course has a property TotalWeight which calculates the total weight of all students. If the weight of a student changes, or a student is added/removed to the course we have to update the value on the UI. This due to INotifyPropertyChanged and ObservableCollection.

If you use LinqToSQL or even better EntityFramework you can get from store. You could than inject it into the ViewModel constructor as the acutal model. Maybe you even want to introduce a MainViewModel for this purpose. I skipped this for simplicity. But you may want to have a look on dependency injection.

XAML:

    <DataGrid ItemsSource="{Binding}">
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <DataGrid ItemsSource="{Binding Students}"></DataGrid>
            </DataTemplate>
        </DataGrid.RowDetailsTemplate>
    </DataGrid>

code behind:

public class CourseViewModel : ViewModelBase
{
    #region Fields

    private string _name;

    private ObservableCollection<StudentViewModel> _students;

    #endregion Fields

    #region Constructors

    public CourseViewModel()
    {
        _students = new ObservableCollection<StudentViewModel>();
        _students.CollectionChanged += _students_CollectionChanged;
    }

    #endregion Constructors

    #region Properties

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public ObservableCollection<StudentViewModel> Students
    {
        get
        {
            return _students;
        }
    }

    public double TotalWeight
    {
        get
        {
            return Students.Sum(x => x.Weight);
        }
    }

    #endregion Properties

    #region Methods

    private void _students_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {

        // add/remove property changed handlers to students 
        if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
        {
            foreach (StudentViewModel student in e.NewItems)
            {
                student.PropertyChanged += Student_PropertyChanged;
            }
        }
        if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
        {
            foreach (StudentViewModel student in e.OldItems)
            {
                student.PropertyChanged -= Student_PropertyChanged;
            }
        }

        //students were added or removed to the course -> inform "listeners" that TotalWeight has changed
        OnPropertyChanged(nameof(TotalWeight));
    }

    private void Student_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //the weight of a student has changed -> inform "listeners" that TotalWeight has changed
        if (e.PropertyName == nameof(StudentViewModel.Weight))
        {
            OnPropertyChanged(nameof(TotalWeight));
        }
    }

    #endregion Methods
}

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{

    #region Constructors

    public MainWindow()
    {
        InitializeComponent();


        var course1 = new CourseViewModel() { Name = "Course1" };
        course1.Students.Add(new StudentViewModel() { Weight = 100, Name = "Mark" });
        course1.Students.Add(new StudentViewModel() { Weight = 120, Name = "Olaf" });
        course1.Students.Add(new StudentViewModel() { Weight = 111, Name = "Hans" });
        var course2 = new CourseViewModel() { Name = "Course2" };
        course2.Students.Add(new StudentViewModel() { Weight = 100, Name = "Mark" });
        course2.Students.Add(new StudentViewModel() { Weight = 90, Name = "Renate" });
        course2.Students.Add(new StudentViewModel() { Weight = 78, Name = "Judy" });

        DataContext = new List<CourseViewModel>()
        {
            course1,
            course2
        };
    }

    #endregion Constructors


}
public class StudentViewModel : ViewModelBase
{
    #region Fields

    private string _name;
    private double _weight;

    #endregion Fields

    #region Properties

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public double Weight
    {
        get
        {
            return _weight;
        }
        set
        {
            _weight = value;
            OnPropertyChanged();
        }
    }

    #endregion Properties
}
Mat
  • 1,960
  • 5
  • 25
  • 38