Question

A while ago i wrote an "Attached Behavior" for two way syncronisation between a XamDataGrid and a "Business Object" as ObservableCollection. The XamDataGrid is the source and the ObservableCollection as DataSource is the Target. I did not use a ListCollectionView since for specific reasons.

The problem

When the DataContext of the DataGrid is changed to another Vehicle the currently loaded DataGrid does not update the DependencyProperty of the behavior.

I cannot figure out why.

The only solution i can think of is to hook the DataContextChanged of the DataGrid and do a new BindingOperation with a Path that is then set relative to the DataContext to figure out the SelectedItems property. But in that case the DependencyProperty of the behavior should be set to a Path to the SelectedItems property and not a binding.

Having the following classes

A example model

public class Vehicle 
{ 
    public PassengerList Passengers { get; set; } 
}

public class PassengerList : ObservableCollection<Passenger> 
{
    public PassengerList()
    {
         SelectedPassengers = new ObservableCollection<Passenger>();
    }

    public ObservableCollection<Passenger> SelectedPassengers { get; private set; }
}

public class Passenger
{
    public string Name { get; set; }
}

The xaml

<igDG:XamDataGrid DataSource="{Binding Passengers}">
<i:Interaction.Behaviors>
   <b:XamDataGridSelectedItemsBehavior SelectedItems="{Binding Path=Passengers.SelectedPssengers}" />
</i:Interaction.Behaviors>
</igDG:XamDataGrid>

PS: I have also tried a Element binding to the DataGrid as element but that doesn't fix it. The DependencyProperty is set only once. For example the Models

The two way behavior

When an selected item changes in the model the grid selected item has to be updated as well. It also does not need to be detached as well, using weak events.

public class XamDataGridSelectedItemsBehavior : Behavior<XamDataGrid>, IWeakEventListener
{
    #region Properties

    private XamDataGrid Grid
    {
        get { return AssociatedObject as XamDataGrid; }
    }

    public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
        "SelectedItems",
        typeof(INotifyCollectionChanged),
        typeof(XamDataGridSelectedItemsBehavior),
        new UIPropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));

    private static void OnSelectedItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
    {
         if (obj != null) 
         {
            (obj as XamDataGridSelectedItemsBehavior).SelectedItems = (e.NewValue as INotifyCollectionChanged);
        }
    }

    public INotifyCollectionChanged SelectedItems
    {
        get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
        set
        {
            // remove old listener
            if (SelectedItems != null)
                CollectionChangedEventManager.RemoveListener(SelectedItems, this);

            SetValue(SelectedItemsProperty, value);

            // add new listener
            if (SelectedItems != null)
                CollectionChangedEventManager.AddListener(SelectedItems, this);
        }
    }

    #endregion

    #region Init

    /// <summary>
    /// Hook up event listeners to the associated object.
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();

        SelectedItemsChangedEventManager.AddListener(Grid, this);
        XamDataGridRecordActivatedEventManager.AddListener(Grid, this);
        XamDataGridLoadedEventManager.AddListener(Grid, this);
    }

    void Grid_RecordActivated(object sender, RecordActivatedEventArgs e)
    {
        if (_transferingToTarget)
            return;

        // if the CellClickAction is EnterEditModeIfAllowed, the grid does not always select the actual record
        // In our case we want it to always select the record
        if (e.Record.DataPresenter.FieldSettings.CellClickAction == CellClickAction.EnterEditModeIfAllowed)
        {
            TransferSourceToTarget();
        }
    }

    void Grid_Loaded(object sender, RoutedEventArgs e)
    {
        TransferTargetToSource(true);
    }

    #endregion

    #region Target to Source

    /// <summary>
    /// When selected items in the target as model has changed, then transfer selected item to grid as the source.
    /// Not when transfering from grid to selected items.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void SelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (_transferingToTarget)
            return;

        TransferTargetToSource(false);
    }

    private bool _transferingToSource = false;
    /// <summary>
    /// Transfer selected item in the target as model to the grid as source.
    /// </summary>
    private void TransferTargetToSource(bool notifyTargetListeners)
    {
        if (SelectedItems == null)
            return;

        List<Record> newSelection = new List<Record>();
        foreach (var item in (SelectedItems as IList))
        {
            var record = Grid.Records.FirstOrDefault(r => (r is DataRecord) && ((r as DataRecord).DataItem == item));
            if (record != null)
            {
                newSelection.Add(record);
            }
        }

        _transferingToSource = true;
        try
        {
            Grid.SelectedItems.Records.Clear();
            Grid.SelectedItems.Records.AddRange(newSelection.ToArray());
            if ((newSelection.Count > 0) && !newSelection.Contains(Grid.ActiveRecord))
            {
                Grid.ActiveRecord = newSelection.FirstOrDefault();
                Grid.ActiveRecord.IsSelected = true;
            }

            if (notifyTargetListeners)
            {
                // Hack to notify the target listeners
                (SelectedItems as IList).Clear();
                foreach (var record in newSelection)
                {
                    (SelectedItems as IList).Add((record as DataRecord).DataItem);
                }
            }
        }
        finally
        {
            _transferingToSource = false;
        }
    }

    #endregion

    #region Source to Target

    /// <summary>
    /// When selected items in the source as grid has changed, then transfer selected item to model as the target.
    /// Not when transfering from selected items to grid.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void Grid_SelectedItemsChanged(object sender, SelectedItemsChangedEventArgs e)
    {
        if (_transferingToSource)
            return;

        TransferSourceToTarget();
    }

    private bool _transferingToTarget = false;
    /// <summary>
    /// Transfer the selected item in the grid as source to the selected item in the target as model.
    /// </summary>
    private void TransferSourceToTarget()
    {
        var target = this.SelectedItems as IList;
        if (target == null)
            return;

        _transferingToTarget = true;
        try
        {
            // clear the target first
            target.Clear();

            // When no item is selected there might still be an active record
            if (Grid.SelectedItems.Count() == 0)
            {
                if (Grid.ActiveDataItem != null)
                    target.Add(Grid.ActiveDataItem);
                else if (Grid.ActiveRecord != null && Grid.ActiveRecord.IsDataRecord)
                    target.Add((Grid.ActiveRecord as DataRecord).DataItem);
                else if (Grid.ActiveCell != null && Grid.ActiveCell.Record != null && Grid.ActiveCell.Record.IsDataRecord)
                    target.Add((Grid.ActiveCell.Record as DataRecord).DataItem);
            }
            else
            {
                // foreach record in the source add it to the target
                foreach (var r in Grid.SelectedItems.Records)
                {
                    if (r.IsDataRecord)
                    {
                        target.Add((r as DataRecord).DataItem);
                    }
                }
            }
        }
        finally
        {
            _transferingToTarget = false;
        }
    }

    #endregion

    /// <summary>
    /// Receive an event and delegate it to the correct eventhandler.
    /// </summary>
    /// <param name="managerType"></param>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// <returns></returns>
    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
        if (managerType == typeof(CollectionChangedEventManager))
        {
            SelectedItems_CollectionChanged(sender, e as NotifyCollectionChangedEventArgs);
            return true;
        }
        else if (managerType == typeof(SelectedItemsChangedEventManager))
        {
            Grid_SelectedItemsChanged(sender, e as SelectedItemsChangedEventArgs);
            return true;
        }
        else if (managerType == typeof(XamDataGridRecordActivatedEventManager))
        {
            Grid_RecordActivated(sender, e as RecordActivatedEventArgs);
            return true;
        }
        else if (managerType == typeof(XamDataGridLoadedEventManager))
        {
            Grid_Loaded(sender, e as RoutedEventArgs);
            return true;
        }
        return false;
    }
}

#region EventManagers

public class CollectionChangedEventManager : WeakEventManagerBase<CollectionChangedEventManager, INotifyCollectionChanged>
{
    protected override void StartListeningTo(INotifyCollectionChanged source)
    {
        source.CollectionChanged += DeliverEvent;
    }

    protected override void StopListeningTo(INotifyCollectionChanged source)
    {
        source.CollectionChanged -= DeliverEvent;
    }
}

public class XamDataGridRecordActivatedEventManager : WeakEventManagerBase<XamDataGridRecordActivatedEventManager, XamDataGrid>
{
    protected override void StartListeningTo(XamDataGrid source)
    {
        source.RecordActivated += DeliverEvent;
    }

    protected override void StopListeningTo(XamDataGrid source)
    {
        source.RecordActivated -= DeliverEvent;
    }
}

public class XamDataGridLoadedEventManager : WeakEventManagerBase<XamDataGridLoadedEventManager, XamDataGrid>
{
    protected override void StartListeningTo(XamDataGrid source)
    {
        source.Loaded += DeliverEvent;
    }

    protected override void StopListeningTo(XamDataGrid source)
    {
        source.Loaded -= DeliverEvent;
    }
}

public class SelectedItemsChangedEventManager : WeakEventManagerBase<SelectedItemsChangedEventManager, XamDataGrid>
{
    protected override void StartListeningTo(XamDataGrid source)
    {
        source.SelectedItemsChanged += DeliverEvent;
    }

    protected override void StopListeningTo(XamDataGrid source)
    {
        source.SelectedItemsChanged -= DeliverEvent;
    }
}

#endregion

#region EventManager base class

// TODO: 10-10-2011 (rdj): Deze class misschien opnemen in het frontend framework? In ieder geval zolang we nog geen .NET 4.5 gebruiken
// http://10rem.net/blog/2012/02/01/event-handler-memory-leaks-unwiring-events-and-the-weakeventmanager-in-wpf-45

/// <summary>
/// Weak event manager base class to provide easy implementation of weak event managers.
/// </summary>
/// <typeparam name="TManager">Type of the manager.</typeparam>
/// <typeparam name="TEventSource">Type of the event source.</typeparam>
public abstract class WeakEventManagerBase<TManager, TEventSource> : WeakEventManager
    where TManager : WeakEventManagerBase<TManager, TEventSource>, new()
    where TEventSource : class
{
    /// <summary>
    /// Adds a listener
    /// </summary>
    /// <param name="source">The source of the event, should be null if listening to static events</param>
    /// <param name="listener">The listener of the event. This is the class that will recieve the ReceiveWeakEvent method call</param>
    public static void AddListener(object source, IWeakEventListener listener)
    {
        CurrentManager.ProtectedAddListener(source, listener);
    }

    /// <summary>
    /// Removes a listener
    /// </summary>
    /// <param name="source">The source of the event, should be null if listening to static events</param>
    /// <param name="listener">The listener of the event. This is the class that will recieve the ReceiveWeakEvent method call</param>
    public static void RemoveListener(object source, IWeakEventListener listener)
    {
        CurrentManager.ProtectedRemoveListener(source, listener);
    }

    /// <inheritdoc/>
    protected sealed override void StartListening(object source)
    {
        StartListeningTo((TEventSource)source);
    }

    /// <inheritdoc/>
    protected sealed override void StopListening(object source)
    {
        StopListeningTo((TEventSource)source);
    }

    /// <summary>
    /// Attaches the event handler.
    /// </summary>
    protected abstract void StartListeningTo(TEventSource source);

    /// <summary>
    /// Detaches the event handler.
    /// </summary>
    protected abstract void StopListeningTo(TEventSource source);

    /// <summary>
    /// Gets the current manager
    /// </summary>
    protected static TManager CurrentManager
    {
        get
        {
            var mType = typeof(TManager);
            var mgr = (TManager)GetCurrentManager(mType);
            if (mgr == null)
            {
                mgr = new TManager();
                SetCurrentManager(mType, mgr);
            }
            return mgr;
        }
    }
}

#endregion
Was it helpful?

Solution

I have it working by adding two new Dependency Properties.

DataContextProperty

    public static readonly DependencyProperty DataContextProperty = DependencyProperty.Register(
        "DataContext",
        typeof(object),
        typeof(XamDataGridSelectedItemsBehavior),
        new PropertyMetadata(DataContextChanged));

    private static void DataContextChanged(object obj, DependencyPropertyChangedEventArgs e)
    {
        var behavior = obj as XamDataGridSelectedItemsBehavior;
        var binding = new Binding(behavior.Path) { Source = e.NewValue };
        BindingOperations.SetBinding(behavior, XamDataGridSelectedItemsBehavior.SelectedItemsProperty, binding);
    }

PathProperty to use to create a new binding on whenever the DataContext has changed

    public static readonly DependencyProperty PathProperty = DependencyProperty.Register(
        "Path",
        typeof(string),
        typeof(XamDataGridSelectedItemsBehavior),
        new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnPathChanged)));

    private static void OnPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var behavior = obj as XamDataGridSelectedItemsBehavior;
        behavior.Path = e.NewValue as string;
    }

    public string Path { get; set; }

The DataContext property is set in the OnAttached, so that the DataContextChanged event is being hooked into

    protected override void OnAttached()
    {
        base.OnAttached();

        SelectedItemsChangedEventManager.AddListener(Grid, this);
        XamDataGridRecordActivatedEventManager.AddListener(Grid, this);
        XamDataGridLoadedEventManager.AddListener(Grid, this);

        BindingOperations.SetBinding(this, XamDataGridSelectedItemsBehavior.DataContextProperty, new Binding());
    }

The SelectedItems dependency property is now private and slightly modified

    public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
        "SelectedItems",
        typeof(INotifyCollectionChanged),
        typeof(XamDataGridSelectedItemsBehavior2),
        new UIPropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));

    private static void OnSelectedItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var behavior = obj as XamDataGridSelectedItemsBehavior;

        if (behavior.SelectedItems != null)
            CollectionChangedEventManager.RemoveListener(behavior.SelectedItems, behavior);

        if (e.NewValue is INotifyCollectionChanged)
        {
            behavior.SelectedItems = e.NewValue as INotifyCollectionChanged;
            CollectionChangedEventManager.AddListener(behavior.SelectedItems, behavior);
        }
    }

    private INotifyCollectionChanged SelectedItems
    {
        get  { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }

using the behavior in xaml

<igDG:XamDataGrid DataSource="{Binding Passengers}">
<i:Interaction.Behaviors>
   <b:XamDataGridSelectedItemsBehavior Path="Passengers.SelectedPassengers" />
</i:Interaction.Behaviors>
</igDG:XamDataGrid>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top