少し前に、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