2 votes

Comment implémenter Master-Detail avec Multi-Selection dans WPF ?

J'ai l'intention de créer un scénario maître-détail typique, c'est-à-dire une collection d'éléments affichés dans un tableau de bord. ListView par le biais d'une liaison de données (DataBinding) avec un ICollectionView et les détails de l'élément sélectionné dans un groupe de contrôles distinct (TextBox, NumUpDowns...).

Aucun problème jusqu'à présent, en fait j'ai déjà mis en œuvre un scénario assez similaire dans un projet plus ancien. Cependant, il devrait être possible de sélectionner plusieurs éléments dans le ListView et obtenir le valeurs partagées affichées dans la vue détaillée. Cela signifie que si tous les éléments sélectionnés ont la même valeur pour une propriété, cette valeur doit être affichée dans la vue détaillée. S'ils n'ont pas la même valeur, le contrôle correspondant doit fournir un indice visuel à l'utilisateur, et aucune valeur ne doit être affichée (ou un état "non défini" dans un contrôle de type CheckBox par exemple). Maintenant, si l'utilisateur modifie la valeur, ce changement doit être appliqué à tous les éléments sélectionnés.

Les autres exigences sont les suivantes :

  • Compatibilité MVVM (c'est-à-dire pas trop de code-behind)
  • Extensibilité (de nouvelles propriétés/types peuvent être ajoutés ultérieurement)

Quelqu'un a-t-il l'expérience d'un tel scénario ? En fait, je pense qu'il s'agit d'un scénario très courant. Cependant, je n'ai trouvé nulle part de détails à ce sujet.

Merci de votre attention !
gehho.

PS : Dans l'ancien projet mentionné ci-dessus, j'avais une solution utilisant une sous-classe du ViewModel qui gère le cas particulier de la multi-sélection. Elle vérifiait l'égalité de tous les éléments sélectionnés et renvoyait les valeurs appropriées. Cependant, cette approche présentait quelques inconvénients et ressemblait en quelque sorte à un hack car (en plus d'autres choses nauséabondes) il était nécessaire de rompre la synchronisation entre la classe ListView et la vue détaillée et la gérer manuellement.

1voto

ScoobyDon Points 11

Je rencontre exactement le même problème.

IsSynchronizedWithCurrentItem = "True" ne conserve que l'élément courant en synchronisation (le dernier élément sélectionné sans tenir la touche ctrl ou shift).

Tout comme vous l'avez probablement fait, j'ai cherché une réponse sur l'internet et je ne l'ai jamais trouvée. Mon scénario comporte une liaison Master>Detail>Detail à 3 niveaux, où chaque niveau est lié à sa propre ListBox.

J'ai mis au point un système qui fonctionne pour l'instant.

Pour mes niveaux Master>Detail>Detail, j'ai créé une CollectionViewSource individuelle pour chaque niveau et j'ai défini CollectionViewSource.Source comme étant l'objet d'entité approprié. Lors de l'événement SelectionChanged de la ListBox liée à MasterView, j'ai effectué un filtrage sur MasterView.View pour récupérer les objets dont la clé primaire Master = clé étrangère Detail.

C'est bâclé, mais si vous avez trouvé une meilleure façon de procéder, je serais ravi de l'entendre.

1voto

Captain Points 325

Dans votre ViewModel, créez une propriété qui sera liée aux SelectedItems de votre ListView.

Créez une autre propriété qui représentera l'objet détaillé des éléments sélectionnés.

La section details (dans le XAML) est liée à cette propriété details (dans le ViewModel).

Chaque fois que la collection d'éléments sélectionnés est modifiée (événement setter/CollectionChanged), vous devez également mettre à jour votre objet details (qui parcourra les propriétés concernées et décidera si elles ont la même valeur ou non).

Une fois qu'une propriété de l'objet détails est modifiée, vous devez itérer sur votre collection d'éléments sélectionnés et y apporter les modifications appropriées.

C'est tout.

J'espère que cela vous aidera

0voto

gehho Points 4523

J'ai utilisé une approche similaire à celle suggérée par le capitaine . J'ai créé une propriété dans mon ViewModel qui représente plusieurs éléments (c'est-à-dire tous les éléments sélectionnés). Dans les accesseurs get et set de la propriété, j'ai utilisé les méthodes suivantes pour déterminer/définir la valeur de la propriété valeurs communes de/pour tous les articles. Cette approche ne fait appel à aucune réflexion, mais utilise des délégués sous la forme d'expressions lambda, ce qui la rend assez rapide.

En guise d'aperçu, voici ma conception de base :

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)

}

Les propriétés se présentent comme suit :

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

Et le code pour le SharedValueHelper ressemble à ceci :

/// <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

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X