I managed to do it :)
I have created 2 classes StaffIncentiveHelperObject and StaffIncentiveLineHelperObject which implement INotifyPropertyChanged. They overlap the existing classes and fire events when changes are made through the accessers.
As you can there are methods to add/remove StaffIncentiveLineHelperObject from the ObservableCollection in StaffIncentiveHelperObject , and these methods also add/remove the original object from the EDM... this is the bit that effectively links the changes made to the view with the database.
public class StaffIncentiveHelperObject : INotifyPropertyChanged//
{
public delegate void CollectionChangedDelegate();
public static CollectionChangedDelegate CollectionChanged;
public StaffIncentiveHelperObject(staffincentive input)
{
this._StaffIncentive = input;
}
private staffincentive _StaffIncentive;
public staffincentive StaffIncentive
{
get { return _StaffIncentive; }
set
{
if (_StaffIncentive == value) return;
_StaffIncentive = value;
OnPropertyChanged("StaffIncentive");
}
}
public decimal? StaffIncentiveIncrement
{
get
{
return _StaffIncentive.increment;
}
set
{
if (_StaffIncentive.increment != value)
{
_StaffIncentive.increment = value;
OnPropertyChanged("StaffIncentive");
}
}
}
public string StaffIncentiveName
{
get
{
return _StaffIncentive.name;
}
set
{
if (_StaffIncentive.name != value)
{
_StaffIncentive.name = value;
OnPropertyChanged("StaffIncentive");
}
}
}
public byte? StaffIncentiveType
{
get
{
return _StaffIncentive.type;
}
set
{
if(_StaffIncentive.type != value)
{
_StaffIncentive.type = value;
OnPropertyChanged("StaffIncentive");
}
}
}
private ObservableCollection<StaffIncentiveLineHelperObject> _StaffIncentiveLines = new ObservableCollection<StaffIncentiveLineHelperObject>();
public ObservableCollection<StaffIncentiveLineHelperObject> StaffIncentiveLines
{
get { return _StaffIncentiveLines; }
set { _StaffIncentiveLines = value; OnPropertyChanged("StaffIncentiveLines"); }
}
public void AddStaffIncentiveLine( staffincentiveline SIL)
{
SIL.staffincentive = this._StaffIncentive;
_StaffIncentive.staffincentiveline.Add(SIL);
StaffIncentiveLines.Add(new StaffIncentiveLineHelperObject(SIL, this));
//OnPropertyChanged("StaffIncentiveLine");
if (CollectionChanged != null)
{
CollectionChanged.Invoke();
}
}
public void RemoveStaffIncentiveLine(StaffIncentiveLineHelperObject SILHO)
{
_StaffIncentive.staffincentiveline.Remove(SILHO.StaffIncentiveLine);
_StaffIncentiveLines.Remove(SILHO);
//OnPropertyChanged("StaffIncentiveLine");
if (CollectionChanged != null)
{
CollectionChanged.Invoke();
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
var x = PropertyChanged;
if (x != null)
x(this, new PropertyChangedEventArgs(name));
}
#endregion
}
public class StaffIncentiveLineHelperObject : INotifyPropertyChanged//, INotifyCo
{
public StaffIncentiveLineHelperObject(staffincentiveline input, StaffIncentiveHelperObject parent)
{
this._StaffIncentiveLine = input;
this.Parent = parent;
}
public StaffIncentiveHelperObject Parent { get; set;}
private staffincentiveline _StaffIncentiveLine;
public staffincentiveline StaffIncentiveLine
{
get { return _StaffIncentiveLine; }
set
{
if (_StaffIncentiveLine == value) return;
_StaffIncentiveLine = value;
OnPropertyChanged("StaffIncentiveLine");
}
}
public decimal? StaffIncentiveLineLbound
{
get
{
return _StaffIncentiveLine.lbound;
}
set
{
if (_StaffIncentiveLine.lbound != value)
{
_StaffIncentiveLine.lbound = value;
OnPropertyChanged("StaffIncentiveLine");
}
}
}
public double? StaffIncentiveLinePercentage
{
get
{
return _StaffIncentiveLine.percentage;
}
set
{
if (_StaffIncentiveLine.percentage != value)
{
_StaffIncentiveLine.percentage = value;
OnPropertyChanged("StaffIncentiveLine");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
var x = PropertyChanged;
if (x != null)
x(this, new PropertyChangedEventArgs(name));
}
#endregion
}
The datacontext of the xaml is set to an ObservableCollection which is simply built from the existing objects of the EDM like this
ObservableCollection<SupportingObjects.StaffIncentiveHelperObject> myIcentives = new ObservableCollection<SupportingObjects.StaffIncentiveHelperObject>();
foreach (staffincentive SI in db_incentives)
{
StaffIncentiveHelperObject SIHO = new StaffIncentiveHelperObject(SI);
myIcentives.Add(SIHO);
foreach (staffincentiveline SIL in SI.staffincentiveline)
{
SIHO.AddStaffIncentiveLine(SIL);
}
}
Then the XAML displays/updates it using the accessers.
<TreeView ItemsSource="{Binding}" Name="MainTree">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding StaffIncentiveLines, Converter={StaticResource IncentiveLineHelperObjectSort}, ConverterParameter=StaffIncentiveLineLbound}">
<StackPanel Orientation="Horizontal" >
<TextBox Text="{Binding StaffIncentiveName}" Width="120" />
<Button Tag="{Binding}" Click="addline" Content="Add Line" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal" Margin="150,0,0,0">
<TextBox Text="{Binding StaffIncentiveLineLbound, StringFormat='{}{0:c}'}" Width="120" />
<TextBox Text="{Binding StaffIncentiveLinePercentage}" Width="120" />
<Button Tag="{Binding}" Click="delLine" Content="Remove"/>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
The sorting is done only at first load and not dynamically while adding lines, which is fine by me. Here's the sort converter (I copied it from another question on the site)
public class IncentiveLineHelperObjectSort : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
System.Collections.ObjectModel.ObservableCollection<StaffIncentiveLineHelperObject> collection = value as System.Collections.ObjectModel.ObservableCollection<StaffIncentiveLineHelperObject>;
ListCollectionView view = new ListCollectionView(collection);
System.ComponentModel.SortDescription sort = new System.ComponentModel.SortDescription(parameter.ToString(), System.ComponentModel.ListSortDirection.Ascending);
view.SortDescriptions.Add(sort);
return view;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
Here's the methods that Add or Remove staffincentivelines when the buttons are pressed.
private void addline(object sender, RoutedEventArgs e)
{
var incentive = ((sender as Button).Tag as StaffIncentiveHelperObject);
incentive.AddStaffIncentiveLine(new staffincentiveline());
TreeViewItem thisTreeViewItem = MainTree.ItemContainerGenerator.ContainerFromItem(incentive) as TreeViewItem;
thisTreeViewItem.IsExpanded = true;
}
private void delLine(object sender, RoutedEventArgs e)
{
var line = ((sender as Button).Tag as StaffIncentiveLineHelperObject);
StaffIncentiveHelperObject thisIncentive = line.Parent;
thisIncentive.RemoveStaffIncentiveLine(line);
TreeViewItem thisTreeViewItem = MainTree.ItemContainerGenerator.ContainerFromItem(thisIncentive) as TreeViewItem;
if (thisIncentive.StaffIncentiveLines.Count == 0)
{
thisTreeViewItem.IsExpanded = false;
}
}
If someone else has a better way, or means to improve this, I'd be happy to hear it :)