2

少し前に、XamDataGrid と ObservableCollection としての「ビジネス オブジェクト」の間の双方向の同期のための「アタッチされた動作」を書きました。XamDataGrid はソースであり、DataSource としての ObservableCollection はターゲットです。特定の理由から、私は ListCollectionView を使用しませんでした。

問題

DataGrid の DataContext が別の Vehicle に変更された場合、現在ロードされている DataGrid は動作の DependencyProperty を更新しません。

理由がわかりません。

私が考えることができる唯一の解決策は、DataGrid の DataContextChanged をフックし、DataContext に対して相対的に設定される Path を使用して新しい BindingOperation を実行し、SelectedItems プロパティを把握することです。ただし、その場合、動作の DependencyProperty は、バインディングではなく、SelectedItems プロパティへのパスに設定する必要があります。

次のクラスを持つ

モデル例

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; }
}

xaml

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

PS: 要素として DataGrid への Element バインディングも試しましたが、修正されません。DependencyProperty は 1 回だけ設定されます。たとえば、モデル

双方向の振る舞い

モデルで選択された項目が変更されると、グリッドで選択された項目も更新する必要があります。また、弱いイベントを使用して切り離す必要もありません。

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
4

1 に答える 1

3

2 つの新しい依存関係プロパティを追加することで機能します。

DataContext プロパティ

    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);
    }

DataContext が変更されたときに新しいバインディングを作成するために使用するPathProperty

    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; }

DataContext プロパティは OnAttached で設定されるため、DataContextChanged イベントがフックされます。

    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());
    }

SelectedItems 依存関係プロパティが非公開になり、わずかに変更されました

    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); }
    }

xaml で動作を使用する

<igDG:XamDataGrid DataSource="{Binding Passengers}">
<i:Interaction.Behaviors>
   <b:XamDataGridSelectedItemsBehavior Path="Passengers.SelectedPassengers" />
</i:Interaction.Behaviors>
</igDG:XamDataGrid>
于 2012-11-15T13:08:33.313 に答える