Reputation: 292
I have two list boxes that both hold collections. The current setup is so that when a item is selected in the left listbox, you can click a button to add that selected state the right listbox. There is an add and remove button for the listboxes that are tied to a custom command with the listbox selected item being the command parameter.
I would like to add a double click functionality to each box so that items can be double clicked to add and remove. I should be able to use my current command execute methods to do this, but have not found a solution to implementing this into a listbox, or listboxitem. I would like to follow MVVM as much as possible, but I've already side stepped that a bit with the current execute methods as i'll show below, but any help would be appreciated. I have not had luck finding anything regarding my specific issue.
<ListBox x:Name="List" ItemContainerStyle="{StaticResource ListBoxItem}" DataContext="{StaticResource VM}"
ItemsSource="{Binding Names, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" DisplayMemberPath="Name"
Style="{StaticResource ResourceKey=ListBox}"/>
<Button Content=">>" Margin="5" Style="{StaticResource ResourceKey=MultiButton}"
CommandParameter="{Binding ElementName=List}"
Command="{Binding Path=AddSelectedItemCommand}"/>
public void AddSelectedItem(object obj)
{
ListBox ListBox = obj as ListBox;
List<Type> List = new List<Type>();
if (Name == null)
Name = new ObservableCollection<Type>();
if (Name != null)
{
foreach (Type item in ListBox.SelectedItems.Cast<object>().ToList())
{
List.Add(item);
Names.Remove(item);
}
foreach (Type listItem in List)
{
var state = Name.FirstOrDefault(aa => aa.Name == listItem.Name);
if (state == null)
{
Name.Add(listItem);
}
}
}
OnPropertyChanged("Name");
OnPropertyChanged("Names");
}
Upvotes: 0
Views: 3341
Reputation: 7102
Credit to Bill, as the UWP edit pointed me toward a satisfactory solution.
Firstly, I added a NuGet reference to Microsoft.Xaml.Behaviors.Uwp.Managed
Secondly I added the namespaces Bill mentions to the xaml in which my control is located:
xmlns:i="using:Microsoft.Xaml.Interactivity" xmlns:core="using:Microsoft.Xaml.Interactions.Core"
Then I added some XAML in my control (List View in this example):
<ListView ...>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="DoubleTapped">
<core:InvokeCommandAction Command="{Binding NavigateUpCommand, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
...
</ListView>
In my case, this was a templated control - and the "DoubleTapped" event name was used successfully :)
The Command was set up in the best way I know; made available as an ICommand
get
accessor on the in the control class, which used a stock "RelayCommand" implementation
Upvotes: 0
Reputation: 172
Firstly I would like to let you know that your View Model should know nothing at all about the View itself, so it should know nothing about ListBoxes.
Objects should only know about they things which they depend upon, and not those which depend upon it. Therefore the ViewModel should only know about the collections of data which it is making available to any client.
In your example, what happens when the control is changed from a ListBox -you will have to change your Command.
So, first things first, you will need to change your view model implementation, what you have currently is not MVVM.
Here is an entire listing which should help you along your way:
<Window x:Class="WpfExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfExample"
mc:Ignorable="d"
Title="MainWindow" Height="140" Width="410">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Path=Names, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedName}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=DataContext.MyDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Column="1" Margin="10,0,0,0" ItemsSource="{Binding Path=NamesTwo, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedNameTwo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=DataContext.MyOtherDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And the code behind
namespace WpfExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
}
Then there is the ViewModel, which you should notice only modifies the collections which are exposed for the View to consume
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Prism.Commands;
namespace WpfExample
{
public class MyViewModel : INotifyPropertyChanged
{
private string _selectedName;
private string _selectedNameTwo;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<string> Names { get; }
= new ObservableCollection<string>(new List<string>
{
"Name1",
"Name2",
"Name3",
"Name4",
"Name5"
});
public ObservableCollection<string> NamesTwo { get; } = new ObservableCollection<string>(new List<string>());
public string SelectedName
{
get { return _selectedName; }
set { _selectedName = value; OnPropertyChanged(); }
}
public string SelectedNameTwo
{
get { return _selectedNameTwo; }
set { _selectedNameTwo = value; OnPropertyChanged(); }
}
public ICommand MyOtherDoubleClickCommand
{
get
{
return new DelegateCommand<string>(name =>
{
NamesTwo.Remove(name);
Names.Add(name);
SelectedNameTwo = "";
});
}
}
public ICommand MyDoubleClickCommand
{
get
{
return new DelegateCommand<string>(name =>
{
Names.Remove(name);
NamesTwo.Add(name);
SelectedName = "";
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I have used the Prism.Core package for the DelegateCommand object. This is not essential, I just did it for ease
You don't even need the SelectedName and SelectedNameTwo properties if they will not be used whilst processing the ViewModel. I included them for completeness.
.
Edited: I did not originally notice that this is for a UWP project. I believe the following will work -though it is untested here since I am not set up for UWP on my machine at the moment. I'm not certain of the
DoubleClick
EventName.
<Page xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core>
<ListBox Grid.Column="1" Margin="10,0,0,0" ItemsSource="{Binding Path=NamesTwo, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedNameTwo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}" >
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="DoubleClick">
<core:InvokeCommandAction Command="{Binding Path=DataContext.MyDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Page, Mode=FindAncestor} }"
CommandParameter="{Binding .}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Upvotes: 2