EventToCommandTrigger SlectionChanged

Nov 1, 2011 at 3:24 PM

Hi.
I am trying to use the trigger to fire ListView selection change into my viewmodel.
At some point ListView is unloaded from visual tree , all the events are fired correctly untill now.
Once ListView is reloaded to the visual tree the event is not fired at all and creates inconsistency between the actuall selected items in my view model and the ListView. 
It seems that when reloaded the triggers Command property is null?

Is there any way this can be fixed?


Coordinator
Nov 1, 2011 at 5:09 PM
Edited Nov 1, 2011 at 5:11 PM

Sounds more a problem with Blend Interactivity Dlls then Cinch.

 

Anyway I would not handle selection as you are currently doing it.

As ListView inherits from Selector (so it has SelectedItem) I would use one of these techniques:

 

  1. I would have a property on my VM which the ListView would bind its SelectedItem to.
  2. Use ICollectionView : Marlon Grech has good write up on how to do this : http://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/ (though I think option 1 is the best option, keep it simple and all that)

 

Anway what you are describing seems quite outside my control really, I think this is the Blend Interactivity Dlls.

Nov 1, 2011 at 5:30 PM

I already have SlectedItem property on my MVVM and its data bound.
In my case i need to have multiple selection passed from ListView.
I will check out the post you mention , thank you very much for your help.

Nov 1, 2011 at 6:42 PM

What i did is that instead of hooking SelectionChanged event i hook Selectors Loaded event and passing the Selector instance to my model.
In my model i can hook any Selector event i want and it works and all selection events are raised correctly [till now].

What do you think of this approach?

Coordinator
Nov 2, 2011 at 8:58 AM

No that is really bad idea. That is bad seperation of concerns, and you should never (in my opinion) have UI specifics in your VM.

 

What you should do is used attached behavior which you apply to selector (ListView in your case), which can provide VM with selected items.

 

Here is how

 

The attached behaviour (you can change this to Blend behaviour if you want, I am just using standard attached DP)

 

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Collections;
using System.Windows.Input;


namespace SomeNameSpace
{
    /// <summary>
    /// Provides a generic Selector, multi selection behaviour that allows the ViewModel
    /// to be populated with the currently selected Selector items
    /// </summary>
    public static class MultiSelectBehaviour
    {
        #region SelectedItems

        /// <summary>
        /// SelectedItems Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(MultiSelectBehaviour),
                new FrameworkPropertyMetadata(null,
                    new PropertyChangedCallback(OnSelectedItemsChanged)));

        /// <summary>
        /// Gets the SelectedItems property.  
        /// </summary>
        public static IList GetSelectedItems(DependencyObject d)
        {
            return (IList)d.GetValue(SelectedItemsProperty);
        }

        /// <summary>
        /// Sets the SelectedItems property.  
        /// </summary>
        public static void SetSelectedItems(DependencyObject d, IList value)
        {
            d.SetValue(SelectedItemsProperty, value);
        }

        /// <summary>
        /// Handles changes to the SelectedItems property.
        /// </summary>
        private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //if in design time do not allow 
            if (DesignerProperties.GetIsInDesignMode(d) || d == null)
                return;

            if (d is ListView)
            {
                ListView listview = (ListView)d;
                listView.SelectionChanged -= selector_SelectionChanged;
                listView.SelectionChanged += selector_SelectionChanged;
            }

        }



        private static void SelectionChanged(DependencyObject d, IList controlSelectedItems)
        {
            IList selectedItems = GetSelectedItems(d);

            if (controlSelectedItems != null && selectedItems != null)
            {
                selectedItems.Clear();
                foreach (object item in controlSelectedItems)
                    selectedItems.Add(item);
            }

            //run an ICommand in the ViewModel which in turn can notified interested parties that
            //come new SelectedItems are available
            ICommand commandToCallWhenSelectionChanges = GetTheCommandToRun(d);
            if (commandToCallWhenSelectionChanges != null)
            {
                commandToCallWhenSelectionChanges.Execute(null);
            }
        }

        static void selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ListView listView = sender as ListView;
            if (listView != null)
            {
                SelectionChanged(listView, listView.SelectedItems);
            }
        }

        #endregion

        #region TheCommandToRun

        /// <summary>
        /// TheCommandToRun : The actual ICommand to run
        /// </summary>
        public static readonly DependencyProperty TheCommandToRunProperty =
            DependencyProperty.RegisterAttached("TheCommandToRun",
                typeof(ICommand),
                typeof(MultiSelectBehaviour),
                new FrameworkPropertyMetadata((ICommand)null));

        /// <summary>
        /// Gets the TheCommandToRun property.  
        /// </summary>
        public static ICommand GetTheCommandToRun(DependencyObject d)
        {
            return (ICommand)d.GetValue(TheCommandToRunProperty);
        }

        /// <summary>
        /// Sets the TheCommandToRun property.  
        /// </summary>
        public static void SetTheCommandToRun(DependencyObject d, ICommand value)
        {
            d.SetValue(TheCommandToRunProperty, value);
        }
        #endregion
    }
}

 

The View using this attached DP would be like this


<UserControl x:Class="SomeView"
	xmlns:local="clr-namespace:SomeNameSpace">


	<ListView
        	ItemsSource="{Binding ListOfObjects}" 
        	SelectionMode="Multiple"
		local:MultiSelectBehaviour.TheCommandToRun="{Binding SelectionChangedCommand}"
        	local:MultiSelectBehaviour.SelectedItems="{Binding SelectedObjects}"/>

</UserControl>

 

The ViewModel would be like this

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;


namespace SomeNameSpace
{
    public class SomeViewModel : INPCBase
    {
        private List<SomeClass> selectedObjects = new List<SomeClass>();
        public SomeViewModel()
        {

            SelectionChangedCommand = new SimpleCommand(ExecuteSelectionChangedCommand);
        }


        public ICommand SelectionChangedCommand { get; private set; }
        public IEnumerable<SomeClass> ListOfObjects { get; private set; }



        public List<SomeClass> SelectedObjects 
        {
            get { return selectedObjects; }
            set
            {
                selectedObjects = value;
                NotifyChanged("SelectedObjects");
            }
        }


        #region Command Handlers

        private void ExecuteSelectionChangedCommand(object parameter)
        {
            //Do you code here that relies on the selection changing
        }
        #endregion
    }
}

 

That is how you do it, clean seperation of concerns. No UI junk in VM. Enjoy

Nov 2, 2011 at 9:42 AM

Ok i will try this approach, but there is probability of having the same problem that i mention in the first post.

Thank you for your help Sacha!

Coordinator
Nov 2, 2011 at 10:04 AM

I think it is less likely if you DO NOT use the Blend Interactivity Dlls, that's why I showed you code that just used standard DPs