WPF で複数選択を使用してマスターと詳細を実装するにはどうすればよいですか?
-
20-09-2019 - |
質問
典型的なマスター/ディテールのシナリオを作成する予定です。に表示されるアイテムのコレクション ListView
DataBinding 経由で ICollectionView
, 、および別のコントロール グループ (TextBoxes、NumUpDowns...) で選択された項目に関する詳細。
ここまでは問題ありません。実際、かなり似たシナリオを古いプロジェクトですでに実装しています。ただし、できるはずです 複数の項目を選択する ListView で適切な値を取得します 共通の価値観が表示される 詳細ビューで。つまり、選択したすべての項目のプロパティ値が同じである場合、その値が詳細ビューに表示される必要があります。それらが同じ値を共有していない場合、対応するコントロールはこれを示す視覚的な手がかりをユーザーに提供する必要があり、値は表示されません (または、「未定義」状態になります)。 CheckBox
例えば)。ここで、ユーザーが値を編集すると、この変更が適用されます。 全て 選択されたアイテム。
さらなる要件は次のとおりです。
- MVVM 互換性 (つまり、コードビハインドが多すぎない)
- 拡張性 (新しいプロパティ/タイプを後で追加できます)
このようなシナリオを経験した人はいますか?実際、これは非常に一般的なシナリオであるはずだと思います。しかし、そのトピックに関する詳細はどこにも見つかりませんでした。
ありがとう!
げほ。
追伸: 前述の古いプロジェクトでは、複数選択の特殊なケースを処理する ViewModel のサブクラスを使用したソリューションがありました。選択されたすべての項目が等しいかどうかをチェックし、適切な値を返します。ただし、このアプローチにはいくつかの欠点があり、(他の厄介な問題に加えて) 間の同期を解除する必要があるため、どういうわけかハッキングのように見えました。 ListView
詳細ビューを表示し、手動で処理します。
解決
あなたのViewModelでは、あなたのリストビューのselectedItemsのに結合するプロパティを作成します。
あなたの選択した項目の詳細オブジェクトを表します別のプロパティを作成します。
(XAML)に詳細セクションには、(ビューモデルで)プロパティの詳細をこれに結合されている。
選択した項目のコレクションが変更されるたびに(セッター/て、CollectionChangedイベントが)、あなたはあなたの詳細を更新する必要があります(つまり、関連するプロパティを反復処理し、それらが同じ値かいないのなら決定します)だけでなく、オブジェクト。
詳細内のプロパティが変更されたオブジェクトたら、あなたはあなたの選択した項目のコレクションに戻って反復処理し、それらに関連する変更を加える必要があります。
それはそれだ。
それがお役に立てば幸いです。
他のヒント
私はこの全く同じ問題に実行しています。
IsSynchronizedWithCurrentItem = "True"
は同期してのCurrentItem(CtrlキーまたはShiftキーを押しせずに選択した最後の項目)を保持します。
あなたはおそらく持っているのと同じように、私は答えを広範囲にインターネットを検索し、1思い付いたことがありません。私のシナリオでは、各階層は、それ自身のListBoxにバインドされた3層のマスター>詳細>詳細結合を、持っています。
私は当分のために働く何かを装備しました。
は、私のマスター>詳細>詳細階層のために私は層ごとに個別のCollectionViewSourceを作成し、CollectionViewSource.Sourceその適切なエンティティオブジェクトに設定します。 MasterViewバインドListBoxコントロールのSelectionChangedイベントに私は、オブジェクトを取得するMasterView.Viewにフィルタを行っどこマスター主キー=ディテール外部キーます。
これはずさんだが、あなたはこれを成し遂げるためのより良い方法を発見した場合、私はそれを聞いてみたい。
私は<のhref = "https://stackoverflow.com/questions/2468710/how-to-implement-master-detail-with-multi-selection-in-wpf/3386508#3386508と同様のアプローチを使用していましたキャプテンにによって提案「> 1。私は複数の項目(すなわち、選択したすべての項目)を表して、私のViewModel内の1つのプロパティを作成しました。プロパティGET-およびセットアクセサ内のI / のすべての項目のための/のの共有値を設定するかを決定するために、以下の方法を使用しています。このアプローチは、任意のリフレクションを使用しますが、それはかなり高速になりラムダ式の形でデリゲートを使用しません。
概要として、これが私の基本的なデザインです。
public class MyMultiSelectionViewModel
{
private List<MyItemType> m_selectedItems = new List<MyItemType>();
public void UpdateSelectedItems(IList<MyItemType> selectedItems)
{
m_selectedItems = selectedItems;
this.OnPropertyChanged(() => this.MyProperty1);
this.OnPropertyChanged(() => this.MyProperty2);
// and so on for all relevant properties
}
// properties using SharedValueHelper (see example below)
}
のプロパティは次のようになります:
public string Name
{
get
{
return SharedValueHelper.GetSharedValue<MyItemType, string>(m_selectedItems, (item) => item.Name, String.Empty);
}
set
{
SharedValueHelper.SetSharedValue<MyItemType, string>(m_selectedItems, (item, newValue) => item.Name = newValue, value);
this.OnPropertyChanged(() => this.Name);
}
}
そしてSharedValueHelper
クラスのコードは次のようになります:
/// <summary>
/// This static class provides some methods which can be used to
/// retrieve a <i>shared value</i> for a list of items. Shared value
/// means a value which represents a common property value for all
/// items. If all items have the same property value, this value is
/// the shared value. If they do not, a specified <i>non-shared value</i>
/// is used.
/// </summary>
public static class SharedValueHelper
{
#region Methods
#region GetSharedValue<TItem, TProperty>(IList<TItem> items, Func<TItem, TProperty> getPropertyDelegate, TProperty nonSharedValue)
/// <summary>
/// Gets a value for a certain property which represents a
/// <i>shared</i> value for all <typeparamref name="TItem"/>
/// instances in <paramref name="items"/>.<br/>
/// This means, if all wrapped <typeparamref name="TItem"/> instances
/// have the same value for the specific property, this value will
/// be returned. If the values differ, <paramref name="nonSharedValue"/>
/// will be returned.
/// </summary>
/// <typeparam name="TItem">The type of the items for which a shared
/// property value is requested.</typeparam>
/// <typeparam name="TProperty">The type of the property for which
/// a shared value is requested.</typeparam>
/// <param name="items">The collection of <typeparamref name="TItem"/>
/// instances for which a shared value is requested.</param>
/// <param name="getPropertyDelegate">A delegate which returns the
/// property value for the requested property. This is used, so that
/// reflection can be avoided for performance reasons. The easiest way
/// is to provide a lambda expression like this:<br/>
/// <code>(item) => item.MyProperty</code><br/>
/// This expression will simply return the value of the
/// <c>MyProperty</c> property of the passed item.</param>
/// <param name="nonSharedValue">The value which should be returned if
/// the values are not equal for all items.</param>
/// <returns>If all <typeparamref name="TItem"/> instances have
/// the same value for the specific property, this value will
/// be returned. If the values differ, <paramref name="nonSharedValue"/>
/// will be returned.</returns>
public static TProperty GetSharedValue<TItem, TProperty>(IList<TItem> items, Func<TItem, TProperty> getPropertyDelegate, TProperty nonSharedValue)
{
if (items == null || items.Count == 0)
return nonSharedValue;
TProperty sharedValue = getPropertyDelegate(items[0]);
for (int i = 1; i < items.Count; i++)
{
TItem currentItem = items[i];
TProperty currentValue = getPropertyDelegate(currentItem);
if (!sharedValue.Equals(currentValue))
return nonSharedValue;
}
return sharedValue;
}
#endregion
#region SetSharedValue<TItem, TProperty>(IList<TItem> a_items, Action<TItem, TProperty> a_setPropertyDelegate, TProperty a_newValue)
/// <summary>
/// Sets the same value for all <typeparamref name="TItem"/>
/// instances in <paramref name="a_items"/>.
/// </summary>
/// <typeparam name="TItem">The type of the items for which a shared
/// property value is requested.</typeparam>
/// <typeparam name="TProperty">The type of the property for which
/// a shared value is requested.</typeparam>
/// <param name="items">The collection of <typeparamref name="TItem"/>
/// instances for which a shared value should be set.</param>
/// <param name="setPropertyDelegate">A delegate which sets the
/// property value for the requested property. This is used, so that
/// reflection can be avoided for performance reasons. The easiest way
/// is to provide a lambda expression like this:<br/>
/// <code>(item, newValue) => item.MyProperty = newValue</code><br/>
/// This expression will simply set the value of the
/// <c>MyProperty</c> property of the passed item to <c>newValue</c>.</param>
/// <param name="newValue">The new value for the property.</param>
public static void SetSharedValue<TItem, TProperty>(IList<TItem> items, Action<TItem, TProperty> setPropertyDelegate, TProperty newValue)
{
if (items == null || items.Count == 0)
return;
foreach (TItem item in items)
{
try
{
setPropertyDelegate(item, newValue);
}
catch (Exception ex)
{
// log/error message here
}
}
}
#endregion
#endregion
}