如何在WPF中实现多选的主细节?

时间:2010-03-18 09:38:16

标签: wpf data-binding multi-select master-detail

我计划创建一个典型的Master-Detail场景,即通过DataBinding向ListView显示在ICollectionView中的项目集合,以及单独控件组中所选项目的详细信息( TextBoxes,NumUpDowns ......)。

到目前为止没问题,实际上我已经在旧项目中实现了一个非常类似的场景。但是,应该可以在ListView中选择多个项目,并在详细信息视图中显示相应的共享值。这意味着,如果所有选定项目的属性值相同,则此值应显示在详细信息视图中。如果它们不共享相同的值,则相应的控件应该为用户指示这一点提供一些视觉线索,并且不应显示任何值(或者例如CheckBox中的“未定义”状态)。现在,如果用户编辑了该值,则此更改应应用于所有所选项目。

进一步的要求是:

  • MVVM兼容性(即没有太多的代码隐藏)
  • 可扩展性(稍后可添加新属性/类型)

有没有人有这种情况的经验?实际上,我认为这应该是一个非常常见的情况。但是,我无法在任何地方找到有关该主题的任何细节。

谢谢!
gehho。

PS:在上面提到的旧项目中,我有一个使用ViewModel子类的解决方案,它处理多选的特殊情况。它检查了所有选定项目是否相等并返回了适当的值。然而,这种方法有一些缺点,并且在某种程度上看起来像是一个黑客,因为(除了其他臭的东西),有必要打破ListView和详细视图之间的同步并手动处理它。

3 个答案:

答案 0 :(得分:1)

我遇到了同样的问题。

IsSynchronizedWithCurrentItem = "True"仅保持CurrentItem同步(您选择的最后一项不保持ctrl或shift)。

就像你可能有的一样,我已经在互联网上搜索了一个答案,从来没有想过一个。我的场景有一个3层Master> Detail> Detail绑定,其中每个层绑定到它自己的ListBox。

我已经装好了暂时有用的东西。

For my Master> Detail> Detail tiers我为每个层创建了一个单独的CollectionViewSource,并将CollectionViewSource.Source设置为适当的实体对象。在MasterView绑定ListBox的SelectionChanged事件上,我在MasterView.View上执行了一个过滤器,以检索主主键=详细外键的对象。

这很草率,但如果你找到了更好的方法来完成这件事,我很乐意听到它。

答案 1 :(得分:1)

在ViewModel中,创建一个将绑定到ListView的SelectedItems的属性。

创建另一个属性,代表所选项目的详细信息对象。

详细信息部分(在XAML中)绑定到此详细信息属性(在ViewModel中)。

每次修改选定的项目集合(setter / CollectionChanged事件)时,您还需要更新您的详细信息对象(这将迭代相关属性并确定它们是否具有相同的值)。

修改详细信息对象中的属性后,您需要重新选择所选项目集合并对其进行相关更改。

就是这样。

希望有所帮助

答案 2 :(得分:0)

我使用了与the one suggested by Captain类似的方法。我在我的ViewModel中创建了一个属性,它表示多个项目(即所有选定的项目)。在属性get-和set-accessors中,我使用以下方法来确定/设置/的所有项的共享值。这种方法不使用任何反射,但使用lambda表达式形式的委托,这使得它非常快。

概述,这是我的基本设计:

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

}