1

this is my first post here, I have been trying a few techniques found in other questions but just can't seem to get this working like I want... I am making changes to an existing application (.net 3.5 with WPF and C#, and Entity Framework with sqlserver2008). I am new to both EFDM and WPF. The new version needs to be fully compatible with existing databases from the previous version, without any modification to the existing databases, so I am quite reluctant to changing the datamodel and any objects generated by it.

Anyway, here's my question: I have objects "staffincentive" and "staffincentivelines" from the edm, each staffincentive having 0 to many staffincentiveline attached. I display those as a treeview, and need to be able to dynamically add or remove staffincentiveline.

<TreeView ItemsSource="{Binding}" Name="MainTree">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding staffincentiveline}">
        <TextBox Text="{Binding name}"/>
        <Button Tag="{Binding}" Click="addline" Content="Add Line" />
            <HierarchicalDataTemplate.ItemTemplate>
                <DataTemplate >
                        <TextBox Text="{Binding lbound}"/>
                        <TextBox Text="{Binding percentage}"/>
                        <Button Tag="{Binding}" Click="delLine" Content="Remove"/>
                </DataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

This works fine, however the staffincentivelines are not sorted, but they do need to appear in order (ascending lbound). So I have looked for a solution and found a converter

public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
        {return
((EntityCollection<staffincentiveline>)value).OrderBy(o => o.lbound);}

Then I realised that now when I add or remove a staffincentiveline using the buttons on the screen, the changes do not display. If I understand corrently it's because the incentivelines are displayed through the view provided by the Sort Converter, and the view is not refreshed when changes are made to the collection. If I understand correctly again, this is because EntityCollection doesn't implement INotifyPropertyChanged. If I refresh the view by using
CollectionViewSource.GetDefaultView(_incentives).Refresh(); then all the treeview gets refreshed and all items are collapsed with is quite confusing for the user...

So how can I have both sorting and refreshing working without changing my object classes staffincentive and staffincentiveline? Should I create another class that includes them and also implement INotifyPropertyChanged?

Thanks

H.B.
  • 166,899
  • 29
  • 327
  • 400
NDUF
  • 687
  • 6
  • 14

1 Answers1

0

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 :)

NDUF
  • 687
  • 6
  • 14