2012-11-15 40 views
1

前一段時間,我爲XamDataGrid和「業務對象」之間的雙向同步編寫了「附加行爲」作爲ObservableCollection。 XamDataGrid是源,而ObservableCollection是DataSource是目標。由於特定原因,我沒有使用ListCollectionView。DataContext更改不會更新「附加行爲」中的綁定

問題

在DataGrid的DataContext的改變與另一車輛當前加載DataGrid不更新行爲的DependencyProperty。

我不明白爲什麼。

我能想到的唯一解決方案是鉤住DataGrid的DataContextChanged,然後用一個Path相對於DataContext設置一個新的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作爲元件,但這不能解決它。 DependencyProperty只設置一次。 例如,模型

所述的雙向行爲

當一個選擇項改變該模型中的網格選擇的項目必須被更新。它也不需要被分離,使用弱事件。

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 
+0

應該不是邏輯是在通知改變的回調,而不是制定者略作修改

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

? –

+0

奇怪的是,當我在PropertyChangedCallback中設置一個斷點時,它只會被點擊一次 - >當datagrid被加載時。我試圖用雙向綁定標誌使它成爲一個FrameworkPropertyMetadata,但是它給出了一個異常,即模型(目標)的SelectedItems屬性是隻讀的。 OMG! – rfcdejong

+0

OMG! :(當DataContext的視圖作爲anchestor被改變時,Grid的DataContextChanged事件甚至沒有被觸發。 – rfcdejong

回答

3

我通過添加兩個新的依賴屬性來工作。

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用來創建每當DataContext的改變

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依賴屬性現在是私人和使用XAML中的行爲

<igDG:XamDataGrid DataSource="{Binding Passengers}"> 
<i:Interaction.Behaviors> 
    <b:XamDataGridSelectedItemsBehavior Path="Passengers.SelectedPassengers" /> 
</i:Interaction.Behaviors> 
</igDG:XamDataGrid> 
相關問題