0

I have a simple ListView in my View:

<ListView x:Name="ObjectListView" HorizontalAlignment="Left" Height="105" Margin="253,268,0,0" VerticalAlignment="Top" Width="163" SelectionChanged="ObjectListView_SelectionChanged">
     <TextBox Width="100"/>
     <Button Width="100" Content="Test"/>
     <Label Width="100" Content="Label"/>
</ListView>

In my ViewModel, I have an ObservableCollection, which does a few (to this question irrelevant) things:

public ObservableCollection<Object> ObjectCollection
    {
        get { return _conversionCollection; }
        set
        {
            if (_conversionCollection != value)
            {
                _conversionCollection = value;
                RaisePropertyChanged("ObjectList");
            }
        }
    }

Ultimatively, those Objects naturally land in the Model(edit: through the help of the RaisePropertyChanged and a few functions), but my problem here is the connection between View and ViewModel.

Currently, I have solved it like this (In the View's code-behind):

public MainWindow()
{
    InitializeComponent();
    _viewModel = (RibbonViewModel)base.DataContext;
}

private void ObjectListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _viewModel.ObjectCollection.Clear();
        foreach(Object item in ObjectListView.SelectedItems)
        {
            _viewModel.ObjectCollection.Add(item);
        }
    }

This isn't all too beautiful, so I'd like to do it properly.

Kohnarik
  • 377
  • 2
  • 5
  • 19
  • @MikkoViitala I've read various articles about ICommand, but I cannot seem to understand it completely. This might also be because everyone does it slightly different and I can't recognize a clear enough pattern. Can you give me an idea of how to do it in my situation? – Kohnarik Dec 04 '15 at 08:15
  • Pattern is you define commands and bind to those commands is XAML. Here's really short example https://github.com/mikkoviitala/cross-viewmodel-communication – Mikko Viitala Dec 04 '15 at 08:18
  • @MikkoViitala Thank you. I'll study it thoroughly. – Kohnarik Dec 04 '15 at 08:41
  • OMG, this has nothing to do with commands! – Liero Dec 04 '15 at 09:50
  • Why does `RaisePropertyChanged("ObjectList")` not match the property name? You should consider using the `[CallerMethodNameAttribute]` instead. – Aron Dec 04 '15 at 10:09
  • @Aron Sorry, I've left out a part I found irrelevant for my question. As it does indeed seem confusing, I have edited it. The "ObjectList" refers to my Model. – Kohnarik Dec 04 '15 at 10:17

4 Answers4

3

You just need to bind your ListView SelectedItems to your ObservableCollection, and thus your collection will be updated automatically using the binding. Actually you don't need to add he event to your code behind.

<ListView x:Name="ObjectListView" HorizontalAlignment="Left" Height="105" Margin="253,268,0,0" VerticalAlignment="Top" Width="163" SelectedItems="{Binding Path=ObjectCollection}">
     <TextBox Width="100"/>
     <Button Width="100" Content="Test"/>
     <Label Width="100" Content="Label"/>
</ListView>

EDIT

To achieve what you want try to use Interaction triggers as below

Add below xmlns to your xaml

xmlns:i="http://schemas.microsoft.com/expression//2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

Don't forget to add that reference :

Microsoft.Expression.Interactions System.Windows.Interactivity

<ListView x:Name="ObjectListView" HorizontalAlignment="Left" Height="105" Margin="253,268,0,0" VerticalAlignment="Top" Width="163">
         <TextBox Width="100"/>
         <Button Width="100" Content="Test"/>
         <Label Width="100" Content="Label"/>
         <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
            <ei:ChangePropertyAction TargetObject="{Binding Mode=OneWay}" PropertyName="SelectedItems" Value="{Binding Path=SelectedItems,     ElementName=ObjectListView}"/>
         </i:EventTrigger>
        </i:Interaction.Triggers>
    </ListView>

ViewModel

public System.Collections.IList SelectedItems {
    get {
      return ObjectCollection;
    }
    set {
      ObjectCollection.Clear();
      foreach (var model in value) {
        ObjectCollection.Add(model);
      }
    }
  }
MRebai
  • 5,344
  • 3
  • 33
  • 52
  • Here as well, I don't have a "SelectedItems" property, only a "SelectedItem". I don't know if there's a difference, but with SelectedItem, it does not seem to work. – Kohnarik Dec 04 '15 at 08:43
  • Add SelectedItem property into your ViewModel then bind it how was said and you should avoid a lot of headaches. – A191919 Dec 04 '15 at 09:14
  • @Moez Rebai I get an error saying the "TargetObject"-property could not be found in type "ChangePropertyAction". – Kohnarik Dec 04 '15 at 09:58
1

Basicaly, you need to databind ViewModel.ObjectCollection to ListView.SelectedItems.

WPF controls does not support this by default, however you could extend the control to support this feaure. One way of extending controls is behaviors. There are two types of behaviors:

  1. System.Windows.Interactivity Behaviors.
  2. Behaviors implemented as attached property.

    <ListView my:ListViewExtension.SelectedItems="{Binding ObjectCollection}" />
    

I've decided to use second. Basically you create custom attached property and in DependencyPropertyChanged callback you can "inject" any code to the framework element by attaching to the element' eventhandlers.

public static class ListViewExtentions
{
    public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached(
        "SelectedItems", typeof (IList), typeof (ListViewExtentions), new PropertyMetadata(SelectedItems_PropertyChanged));
    public static void SetSelectedItems(DependencyObject element, IList value)
    {
        element.SetValue(SelectedItemsProperty, value);
    }
    public static IList GetSelectedItems(DependencyObject element)
    {
        return (IList)element.GetValue(SelectedItemsProperty);
    }


    private static void SelectedItems_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var listView = (ListView) d;
        SynchronizeCollections(listView.SelectedItems, (IList)e.NewValue);

        listView.SelectionChanged += (sender, args) =>
        {
            var listviewSelectedItems = ((ListView) sender).SelectedItems;
            var viewmodelCollection = GetSelectedItems((ListView) sender);
            SynchronizeCollections(listviewSelectedItems, viewmodelCollection);
        };
    }

    private static void SynchronizeCollections(IList source, IList target)
    {
        var oldItems = target.OfType<object>().Except(source.OfType<object>()).ToArray();
        var newItems = source.OfType<object>().Except(target.OfType<object>()).ToArray();

        foreach (var oldItem in oldItems) target.Remove(oldItem);
        foreach (var newItem in newItems) target.Add(newItem);
    }
}

you can use propa code snippet to generate attached property

TIP: I recommend you to rename ViewModel.ObjectCollection to ViewModel.SelectedItems, because now it is misleading.

Liero
  • 25,216
  • 29
  • 151
  • 297
  • Thank you. That's exactly what I was searching for. I've renamed my ObjectCollection the way you suggested it. – Kohnarik Dec 04 '15 at 12:14
  • Your solution is awesome to I always like to use behaviors – MRebai Dec 09 '15 at 07:47
  • Feel free to vote up :) It gets little more complicated when you want to also observe the observable collection, but I've done that as well – Liero Dec 09 '15 at 07:49
0

Maybe this example will help you.

<Grid>
    <ListView  HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding ObjectCollection}" SelectedItem="{Binding SelectedItem}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <TextBox Width="100" Grid.Column="0" Text="{Binding Text}"/>
                    <Button Width="100" Content="Test" Grid.Column="1"/>
                    <Label Width="100" Grid.Column="2" Content="{Binding Label}"/>
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

Code:

namespace WpfApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }
}

public class Obj : INotifyPropertyChanged
{
    public string Text { get; set; }

    private string label;
    public string Label
    {
        get
        {
            return this.label;
        }

        set
        {
            this.label = value;
            this.RaisePropertyChaged("Label");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChaged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

public class ViewModel
{
    private Obj selectedItem;
    public Obj SelectedItem
    {
        get
        {
            return this.selectedItem;
        }

        set
        {
            this.selectedItem = value;
            this.selectedItem.Label = value.Text;
        }
    }
    public ObservableCollection<Obj> ObjectCollection { get; set; }
    public ViewModel()
    {
        ObjectCollection = new ObservableCollection<Obj>();
        ObjectCollection.Add(new Obj { Text = "First" });
        ObjectCollection.Add(new Obj { Text = "Second" });
        ObjectCollection.Add(new Obj { Text = "Third" });
    }
}
}
A191919
  • 3,422
  • 7
  • 49
  • 93
  • I used the three basic objects in the ListView for the sake of easier testing. In the end, this List could contain different, custom created objects, which are much more complex. Your example seems a little bit.. too static for my project. – Kohnarik Dec 04 '15 at 10:15
  • You can make observable collection with GenericType. And place there anything you want. Selected item have type of ObservableCollection all seems ok. In what part you see hardnes? – A191919 Dec 04 '15 at 10:23
  • Hmm.. my inexperience might be the problem. If you are certain it works, I will immerse myself in this topic to fully understand it. – Kohnarik Dec 04 '15 at 10:26
-1
<ListView x:Name="Lst" SelectedItem="{Binding ChosenItem}"> ... </ListView>

This will bring chosen item in the ChosenItem DP, in its setter you can simply add the item to the collection.

private static void ChosenItemPropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {            
            controls.Add(e.NewValue.ToString());
        }
AnjumSKhan
  • 9,647
  • 1
  • 26
  • 38
  • his original code does something else. It does not store all items ever selected, but currently selected items only. – Liero Dec 04 '15 at 11:12
  • @Liero ChosenItem will contain the last item selected, and so will be accessed by Binding mechanism multiple times. In the property changed callback, you can add the new value to the collection. – AnjumSKhan Dec 04 '15 at 11:14