2

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

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

进一步的要求是:

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

有没有人有过这种情况的经验?实际上,我认为这应该是一个非常普遍的情况。但是,我在任何地方都找不到有关该主题的任何详细信息。

谢谢!
杰霍。

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

4

3 回答 3

1

我遇到了同样的问题。

IsSynchronizedWithCurrentItem = "True"仅使 CurrentItem 保持同步(您选择的最后一个项目没有按住 ctrl 或 shift)。

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

我已经安装了一些暂时有效的东西。

对于我的 Master>Detail>Detail 层,我为每个层创建了一个单独的 CollectionViewSource,并将 CollectionViewSource.Source 设置为适当的实体对象。在 MasterView 绑定的 ListBox 的 SelectionChanged 事件上,我对 MasterView.View 执行了过滤,以检索 Master Primary Key = Detail Foreign Key 的对象。

这很草率,但如果你找到了更好的方法来完成这项工作,我很想听听。

于 2010-08-02T02:14:02.240 回答
1

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

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

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

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

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

就是这样。

希望能帮助到你

于 2010-08-02T09:30:46.593 回答
0

我使用了类似于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

}
于 2010-08-02T14:23:04.900 回答