I have seen some answers regarding WP8 or others, however it seems that there is no triggers in WP8.1 (Or I am missing something?)
I have a datatemplate bound from the code (it is a hub datatemplate, and I have a mix of static and dynamic hubsections, therefore this datatemplate needs to be set from the code).
This datatemplate is defined in a separate xaml file, it includes a listbox (or listview) with another datatemplate defined for the items.
I need to bind a command on the item's tap or listbox selectionchanged (or something equivalent). However, the tap event defined in the template is not called, therefore I thought of binding a command on an UI element, but these seems not to support Commands neither interactivity triggers.
Any clue on how to handle that? :)
On the example below I don't get the event Item_Tapped nor ListBox_SelectionChanged, I would anyway prefer to bind one of these to a command in the viewmodel.
<DataTemplate x:Key="HubSectionTemplate">
<Grid>
<ListBox ItemsSource="{Binding MyNodes}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="64" Tapped="Item_Tapped" >
<TextBlock Text="{Binding MyText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
This is how it is used from code:
HubSection hs = new HubSection()
{
ContentTemplate = Application.Current.Resources[HUBSECTION_TEMPLATE] as DataTemplate,
DataContext = model,
Tag = model.UniqueId,
};
Hub.Sections.Insert(firstSectIdx + 1, hs);
public class Model
{
public Guid UniqueId {get;set;}
public List<ItemModel> MyNodes {get;set;}
}
public class ItemModel
{
public string MyText {get;set;}
}
PS: The ItemModel is defined in another assembly and therefore should not be edited (the command should be in the Model class if possible)
--- EDIT ---
In order to simplify the problem, I use the following models:
public class Model
{
public Guid UniqueId {get;set;}
public List<ItemModel> MyNodes {get;set;}
public ICommand MyCommand {get;set;}
}
public class ItemModel
{
Model _Model;
public ItemModel(Model m) {_Model = m; }
public string MyText {get;set;}
public ICommand MyCommand { get { return _Model.MyCommand; }}
}
And my (temporary) solution is to use a button in the itemtemplate:
<ListView.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Stretch" Command="{Binding TapCommand}" Height="64">
<TextBlock Text="{Binding MyText}" />
</Button>
</DataTemplate>
</ListView.ItemTemplate>
You can use Behaviors SDK.
In Visual Studio go to 'Tools -> Extension and updates' and install Behaviors SDK (XAML). Then reference it in your project using Add reference dialog.
After that add following namespaces to your page:
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
Now you can register events like tap on your stack panel using following syntax:
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="64">
<TextBlock Text="{Binding MyText}" />
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding YourCommand}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</StackPanel>
</DataTemplate>
However this code only works if your Command is defined in your ItemModel class. If you want to bind to the parent element Command, you can try something like this (not tested):
{Binding ElementName=LayoutRoot, Path=DataContext.ParentCommand}
But I would preferer having command on your ItemModel class
Edit: Solution without Behaviors SDK:
If you are using ListView (or something inherited from ListViewBase) you can use ItemClick event. To make it more reusable and Mvvm friendly you can implement your DependencyProperty like this:
public static class ItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
{
control.ItemClick += OnItemClick;
}
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.ClickedItem))
{
command.Execute(e.ClickedItem);
}
}
}
Then your ListView will look like this:
<ListView
IsItemClickEnabled="True"
helpers:ItemClickCommand.Command="{Binding YourCommand}"
ItemsSource="{Binding MyNodes}"
ItemTemplate="{StaticResource YourDataTemplate}" />
In this case your child item is passed to your command as a parameter, so it should also solve your problem with your Command defined in parent model.
Related
I have a user control which have one TextBox and one Button called View. The TextBox takes index values.
On my Main view, I have one list view which will display all lines in a file.
An ObservableCollection is Bind to this.
What I need is, when index value is entered in the TextBox and View Button is clicked(in the user control), the SelecedIndex of the ListView(in the Main) should be changed to the index value.
How can I achieve this using MVVM?
Also, Please provide the proper approach to do this if I am doing it wrong.
Here is my UserControl Code:
XAML
<UserControl.DataContext>
<VM:IndexView_VM ></VM:IndexView_VM>
</UserControl.DataContext>
<Grid Background="White">
<TextBlock Margin="10,12,168,9" Text="Index : "/>
<TextBox Text="{Binding Index}" x:Name="TB_Index" Margin="53,11,90,8" />
<Button Command="{Binding View_CMD}" x:Name="BT_View" Content="View" Margin="136,11,10,8" />
</Grid>
ViewModel
public class IndexView_VM : ViewModelBase
{
public IndexView_VM()
{
View_CMD = new RelayCommand(_View_CMD);
}
int _Index;
public int Index
{
get { return _Index; }
set
{
_Index = value;
RaisePropertyChanged();
}
}
public RelayCommand View_CMD { get; set; }
internal void _View_CMD(object Parameter)
{
// What to write here?
}
}
Here is the Main View:
XAML
<UserControl.DataContext>
<VM:MainView_VM></VM:MainView_VM>
</UserControl.DataContext>
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="111*"/>
<ColumnDefinition Width="100*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<local:IndexView/>
<local:IndexView/>
<local:IndexView/>
<local:IndexView/>
</StackPanel>
<ListView ItemsSource="{Binding FileData}" x:Name="listView" Grid.Column="1" >
<ListView.View>
<GridView>
<GridViewColumn Header="Data" Width="100"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
View Model
public class MainView_VM : ViewModelBase
{
public MainView_VM()
{
ReadFile();
}
public ObservableCollection<string> FileData { get; set; }
void ReadFile()
{
//I will read file here.
}
}
Here is a very basic example of what you want to do:
Window XAML
<Window.Resources>
<local:StringToIntConverter x:Key="StringToIntConverter" />
</Window.Resources>
<StackPanel>
<TextBlock>Select Index</TextBlock>
<TextBox x:Name="theTextBox" Text="{Binding SelectedIndex,ElementName=theList,Converter={StaticResource StringToIntConverter},Mode=OneWayToSource,UpdateSourceTrigger=Explicit}" />
<Button Click="Button_Click">Select Now</Button>
<ListBox x:Name="theList">
<ListBoxItem>First</ListBoxItem>
<ListBoxItem>Second</ListBoxItem>
<ListBoxItem>Third</ListBoxItem>
</ListBox>
</StackPanel>
Window CodeBehind
private void Button_Click(object sender, RoutedEventArgs e)
{
theTextBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
Explanation
You were asking to set the index on a list from a textbox by clicking a button. We bind the TextBox to the selected index with a couple of settings:
Source: SelectedIndex
ElementName: The List/Target UI Element
Converter: Required to get from String to Int (i wrote on myself)
Mode: OneWayToSource, we force the textbox to only send values to the list and not the other way round
UpdateSourceTrigger: We do not want the binding to auto-update, we want to do this ourselves
To update the binding we use the Click Event of the button.
But what about the view model?
The operation is a View-Only operation, the ViewModel doesn't need to know anything about it, so we should leave it out of the operation. That's why I'm not using a CommandBinding.
Whoops, forgot about the UserControl
If you want to put this in a user control then i suggest that you don't create a ViewModel at all. Also in the user control you don't need DataBinding, only on the outside. Keep it simple:
UserControl XAML
<TextBlock>Select Index</TextBlock>
<TextBox x:Name="theTextBox" />
<Button Click="Button_Click">Select Now</Button>
UserControl CodeBehind
public int Index
{
get { return (int)GetValue(IndexProperty); }
set { SetValue(IndexProperty, value); }
}
public static readonly DependencyProperty IndexProperty =
DependencyProperty.Register("Index", typeof(int), typeof(ListViewIndexSelectorControl), new PropertyMetadata(0));
private void Button_Click(object sender, RoutedEventArgs e)
{
if(int.TryParse(theTextBox.Text, out int result))
{
Index = result;
}
}
MainWindow Usage XAML
<local:ListViewIndexSelectorControl Index="{Binding SelectedIndex,ElementName=theList,Mode=OneWayToSource}" />
<ListBox x:Name="theList">
<ListBoxItem>First</ListBoxItem>
<ListBoxItem>Second</ListBoxItem>
<ListBoxItem>Third</ListBoxItem>
</ListBox>
If you later on need a ViewModel, you can also use the View as the ViewModel for simple controls, just set DataContext = this; in the View's constructor or use a Name on the XAML element and bind the DataContext by ElementName.
If you are using MvvmLight, you could for example use the Messenger class to send a message from the child view model to the parent:
IndexView_VM:
internal void _View_CMD(object Parameter)
{
GalaSoft.MvvmLight.Messaging.Messenger.Default.Send(_Index);
}
MainView_VM:
public class MainView_VM : ViewModelBase
{
public MainView_VM()
{
ReadFile();
GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<int>(this, index => Index = index);
}
public ObservableCollection<string> FileData { get; set; }
int _Index;
public int Index
{
get { return _Index; }
set
{
_Index = value;
RaisePropertyChanged();
}
}
void ReadFile()
{
//I will read file here.
}
}
MainView:
<ListView ItemsSource="{Binding FileData}" x:Name="listView" Grid.Column="1"
SelectedIndex="{Binding Index}">
<ListView.View>
<GridView>
<GridViewColumn Header="Data" Width="100"/>
</GridView>
</ListView.View>
</ListView>
Please refer to Laurent Bugnion's MSDN Magazine article for more information about the Messenger.
MVVM - Messenger and View Services in MVVM: https://msdn.microsoft.com/en-us/magazine/jj694937.aspx
Not sure if I understand this right. You want to set the selected item of your ListBox using the index value you type in that TextBox, is this right?
If this is is right then I have the following considerations:
In this scenario a UserControl is probably overkill. Especially if you are going to never re-use it somewhere else in your UI.
Given the first consideration, all you have to do is move that TextBox to your main view, give it a name and bind the .Text Property to your ListView's SelectedIndex Property.
Example:
<ListView SelectedIndex="{Binding ElementName=txtIndex, Path=Text, Converter={StaticResource StringToIntConverter}, UpdateSourceTrigger=PropertyChanged}" />
That converter (which you would need to implement) is needed because converting a string to an int won't probably happen automatically. UpdateSourceTrigger=PropertyChanged will update the target Property as soon as you type, removing the need for a button.
At that point you should be done.
If you absolutely need that UserControl, you could add a DependencyProperty exposing the TextBox's value. Then you could bind to that Property as in the example above.
I'm basically asking the same question as this person, but in the context of the newer x:Bind.
ViewModels' DataContext is defined like so
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
So whenever I need to bind something I do it explicitely to the ViewModel like so
ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}"
However that doesn't work within templates
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}"> <-- this here is the culprit
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Reading the documentation, I found that using Path should basically reset the context to the page, but this (x:Bind Path=ViewModel.PageResizeEvent didn't work either. I'm still getting Object reference not set to an instance of an object, which should mean that it doesn't see the method (but a null).
Image class:
public class Image {
public int page { get; set; }
public string url { get; set; }
public int width { get; set; }
public int heigth { get; set; }
}
And in the ChapterPageViewModel
private List<Image> _pageList;
public List<Image> pageList {
get { return _pageList; }
set { Set(ref _pageList, value); }
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode,
IDictionary<string, object> suspensionState)
{
Initialize();
await Task.CompletedTask;
}
private async void Initialize()
{
pageList = await ComicChapterGet.GetAsync(_chapterId);
}
public void PageResized(object sender, SizeChangedEventArgs e)
{
//resizing logic happens here
}
We have two problems here:
First, trying to directly bind an event to a event handler delegate
That will never work, simply put.
One way to handle an event on MVVM pattern is by using EventTrigger and ICommand.
It requires a class that implements ICommand. This post will help you if don't know how to do it. I'll call mine DelegateCommand.
Here's how I would refactor it in two steps:
1) Add a Command to the VM:
public class ChapterPageViewModel
{
public ChapterPageViewModel()
{
this.PageResizedCommand = new DelegateCommand(OnPageResized);
}
public DelegateCommand PageResizedCommand { get; }
private void OnPageResized()
{ }
}
2) Bind that Command to the SizeChanged event with EventTrigger and InvokeCommandAction.
<Page (...)
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core">
(...)
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" >
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{x:Bind ViewModel.PageResizedCommand }" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Page>
"But Gabriel", you say, "that didn't work!"
I know! And that's because of the second problem, which is trying to x:Bind a property that does not belong to the DataTemplate class
This one is closely related to this question, so I´ll borrow some info from there.
From MSDN, regarding DataTemplate and x:Bind
Inside a DataTemplate (whether used as an item template, a content
template, or a header template), the value of Path is not interpreted
in the context of the page, but in the context of the data object
being templated. So that its bindings can be validated (and efficient
code generated for them) at compile-time, a DataTemplate needs to
declare the type of its data object using x:DataType.
So, when you do <ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">, you're actually searching for a property named ViewModel on the that models:Image class, which is the DataTemplate's x:DataType. And such a property does not exist on that class.
Here, I can see two options. Choose one of them:
Add that ViewModel as a property on the Image class, and fill it up on the VM.
public class Image {
(...)
public ChapterPageViewModel ViewModel { get; set; }
}
public class ChapterPageViewModel
{
(...)
private async void Initialize() {
pageList = await ComicChapterGet.GetAsync(_chapterId);
foreach(Image img in pageList)
img.ViewModel = this;
}
}
With only this, that previous code should work with no need to change anything else.
Drop that x:Bind and go back to good ol'Binding with ElementName.
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" x:Name="flipView">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{Binding DataContext.PageResizedCommand
, ElementName=flipView}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
This one kind of defeat the purpose of your question, but it does work and it's easier to pull off then the previous one.
my question here is how to know which button is clicked. My buttons are bound to property of type ObservableCollection which contains objects of type Item and I need to use that object in my ViewModel when a button is clicked. Any ideas how to know which button is clicked? I had few ideas, like sending multiple Command Parameters (1.SelectedItems from ListBox 2.The Object from the button) or bind the object from the button to another property of type Item in the ViewModel after the button is clicked in order to use it. Any ideas will be apreciated.
I have this DataTemplate for buttons
<DataTemplate x:Key="ButtonTemplate">
<WrapPanel>
<Button x:Name="OrderButton"
FontSize="10"
Height="80" Width="80"
Content="{Binding Name}"
Command="{Binding OrderCommand,
Source={StaticResource OrderViewModel}}"
CommandParameter="{Binding ElementName=ListBoxUserControl, Path=SelectedItems}">
</Button>
</WrapPanel>
</DataTemplate>
My ViewModel
public class OrderViewModel : ObservableCollection<Order>, INotifyPropertyChanged
{
public CreateOrderCommand CreateOrderCommand { get; set; }
public ObservableCollection<Item> Data { get; set; }
public OrderViewModel()
{
this.CreateOrderCommand = new CreateOrderCommand(this);
DataObservableCollection data= new DataObservableCollection();
Data = data;
}
}
And I populate my buttons like this
<WrapPanel x:Name="OrderButtons">
<ItemsControl ItemTemplate="{StaticResource ButtonTemplate}"
ItemsSource="{Binding Data, Source={StaticResource OrderViewModel}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</WrapPanel>
Simply change the Button.CommandParameter binding to CommandParamter="{Binding}" if you want the data context of the button (i.e. the item from your items source) as the command parameter or,
CommandParameter="{Binding RelativeSource={RelativeSource Self}}" if you want the actual button that was clicked.
First send the Button DataContext using the CommandParameter. To send the SelectedItem of your Listbox you can use
<Listbox SelectedItem="{Binding SelectedItem}"/>
in your Listbox and make a SelectedItem property in your ViewModel.
private YourItemObject mySelectedItem;
public YourItemObject SelectedItem
{
get { return mySelectedItem; }
set
{
value = mySelectedItem
}
Now you can use the SelectedItem in your ViewModel when the Button gets clicket. If you have multiple selections it gets a little bit more tricky ;).
private ButtonClicked(Parameter object)
{
SelectedItem.UsingIt();
if(object is YourButtonDataContext){
YourButtonDataContext.UsingIt();
}
}
Update with MultiSelection:
With Multiselection you have to do your own Listbox.
public class CustomListBox : ListBox
{
public CustomListBox()
{
this.SelectionChanged += CustomListBox_SelectionChanged;
}
void CustomListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList = this.SelectedItems;
}
#region SelectedItemsList
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(CustomListBox), new PropertyMetadata(null));
#endregion
}
In the ViewModel you have to have a property with the SelectedItems.
private IList mySelectedData = new List<SelectedDataObject>();
public IList SelectedData
{
get { return mySelectedData ; }
set
{
if (mySelectedData != value)
{
mySelectedData = value;
RaisePropertyChanged(() => SelectedData);
}
}
}
The XAML Looks like this:
<local:CustomListBox ItemsSource="{Binding YourList}" SelectionMode="Extended" SelectedItemsList="{Binding SelectedData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
...
</local:CustomListBox>
Source for Multiselection in DataGrid is: https://stackoverflow.com/a/22908694/3330227
I have a longlistselector and in each row I have ToggleSwitch and I would like to call http request via my ApiService when ToggleSwitch is changed. I have ApiService class in ViewModel thanks to injection and in ViewModel I have ObservableCollection of Modules which have switches. I bind it with datatemplate and there is no problem with bind ToggleSwitch to bool property. But what should I do in setter of that property?
Model - Modul.cs
public int IsLock
{
get { return isLock; }
set {
Set(() => IsLock, ref isLock, value);
// What should I do here? How call ViewModel method?
}
}
ViewModel - ModuleListViewModel.cs
public ObservableCollection<Module> Modules { get; private set; }
// here I have apiService instance
// and here I could call apiService.Lock(module) and so on
View - part of DataTemplate
<toolkit:ToggleSwitch x:Name="LockSwitch"
IsChecked="{Binding IsLock, Mode=TwoWay}"/>
What's the right aproach for this? Maybe I could have ApiService class in each Modul class but I think that's very bad. I think ViewModel should somehow findout that Model was changed and it should call method.
I suggest using the ToggleSwitch's Command property -- that will get executed every time the user changes the toggle, and will allow you to bind to the parent data context. Use something like this in the XAML:
<ItemsControl x:Name="items" ItemsSource="{Binding Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<toolkit:ToggleSwitch x:Name="LockSwitch"
Command="{Binding ElementName=items,Path=DataContext.LockToggleCommand}"
CommandParameter="{Binding}"
IsChecked="{Binding IsLock, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then just add the "LockToggleCommand" to your main view model, and call the service, eg:
public ObservableCollection<Module> Modules { get; private set; }
public ICommand LockToggleCommand { get; private set; }
public ViewModel()
{
LockToggleCommand = new DelegateCommand<Module>(module => {
apiService.Lock(module);
});
}
Here "DelegateCommand" is just the usual implementation of ICommand -- I am sure that MVVM Light has its own standard implementation.
Edit
I thought that ToggleSwitch supported Command, but since it doesn't, you can take a similar approach using an EventTrigger (if you are willing to add the System.Windows.Interactivity and Microsoft.Expression.Interactions DLLs to your project):
<ItemsControl x:Name="items" ItemsSource="{Binding Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<toolkit:ToggleSwitch x:Name="LockSwitch"
IsChecked="{Binding IsLock, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Toggled">
<ei:CallMethodAction TargetObject="{Binding ElementName=items,Path=DataContext}"
MethodName="OnToggled"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now add the "OnToggled" method to the main view model -- use the "sender" parameter to get the current item, something like this:
public void OnToggled(object sender, RoutedEventArgs e)
{
var toggleSwitch = (ToggleSwitch)sender;
var module = (Module)toggleSwitch.DataContext;
apiService.Lock(module);
}
I have just started learning MVVM. I've made the application from scratch by following this MVVM tutorial (I highly recommend it to all MVVM beginners out there). Basically, what I have created so far is a couple of text boxes where user adds his or her data, a button to save that data which subsequently populates the ListBox with all entries made.
Here's where I got stuck: I want to be able to double-click on a ListBoxItem and to trigger a command that I have created and added to my ViewModel. I don't know how to finish the XAML side, i.e. I don't know how to bind that command to the ListBox(Item).
Here's XAML:
...
<ListBox
Name="EntriesListBox"
Width="228"
Height="208"
Margin="138,12,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Entries}" />
...
Here's ViewModel:
public class MainWindowViewModel : DependencyObject
{
...
public IEntriesProvider Entries
{
get { return entries; }
}
private IEntriesProvider entries;
public OpenEntryCommand OpenEntryCmd { get; set; }
public MainWindowViewModel(IEntriesProvider source)
{
this.entries = source;
...
this.OpenEntryCmd = new OpenEntryCommand(this);
}
...
}
And finally, here's the OpenEntryCommand that I want to be executed once the user double-clicks the item in the EntriesListBox:
public class OpenEntryCommand : ICommand
{
private MainWindowViewModel viewModel;
public OpenEntryCommand(MainWindowViewModel viewModel)
{
this.viewModel = viewModel;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return parameter is Entry;
}
public void Execute(object parameter)
{
string messageFormat = "Subject: {0}\nStart: {1}\nEnd: {2}";
Entry entry = parameter as Entry;
string message = string.Format(messageFormat,
entry.Subject,
entry.StartDate.ToShortDateString(),
entry.EndDate.ToShortDateString());
MessageBox.Show(message, "Appointment");
}
}
Please help, I'd appreciate it.
Unfortunately, only ButtonBase derived controls have the possibility for binding ICommand objects to their Command properties (for the Click event).
However, you can use an API provided by Blend to map an event (like in your case MouseDoubleClick on the ListBox) to an ICommand object.
<ListBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding YourCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
You'll have to define: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and have a reference to System.Windows.Interactivity.dll.
-- EDIT --
This is part of WPF4, but u can use Microsoft.Windows.Interactivity if you're not using WPF4. This dll is from Blend SDK, which doesn't require Blend, from here:
http://www.microsoft.com/downloads/en/details.aspx?FamilyID=f1ae9a30-4928-411d-970b-e682ab179e17&displaylang=en
Update: I found something that should help you. check this link on MVVM Light Toolkit which contains a walkthrough on how to do this, along with a link to the needed libraries. MVVM Light Toolkit is a very interesting framework for applying MVVM with Silverlight, WPF, and WP7.
Hope this helps :)
This is made tricky because of the DoubleClick event. There are a few ways to do this:
Handle the double-click event in code behind, and then manually invoke a command/method on your ViewModel
Use an attached behavior to route the DoubleClick event to your Command
Use a Blend Behavior to map the DoubleClick event to your command
2 and 3 might be more pure, but frankly, 1 is easier, less complex, and not the worst thing in the world. For a one-off case, I'd probably use approach #1.
Now, if you changed your requirements to use, say, a hyperlink on each item, it would be easier. Start out by naming the root element in your XAML - e.g., for a Window:
<Window .... Name="This">
Now, in the DataTemplate for your ListBox items, use something like this:
<ListBox ...>
<ListBox.ItemTemplate>
<DataTemplate>
<Hyperlink
Command="{Binding ElementName=This, Path=DataContext.OpenEntryCmd}"
Text="{Binding Path=Name}"
/>
The ElementName binding lets you resolve the OpenEntryCmd from the context of your ViewModel, rather than the specific data item.
EDIT: I wrote this post as an inexperienced WPF developer, nowadays I'd either use a framework that provides event to command binding, or simply use a button and restyle it. Of course for maximum flexibility this is maybe better.
I find the best way to do this is to create a simple user control wrapper for my content, with dependency properties for the command and parameter.
The reason I did this was due to the Button not bubbling the click event to my ListBox which prevented it from selecting the ListBoxItem.
CommandControl.xaml.cs:
public partial class CommandControl : UserControl
{
public CommandControl()
{
MouseLeftButtonDown += OnMouseLeftButtonDown;
InitializeComponent();
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
if (Command != null)
{
if (Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
}
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand),
typeof(CommandControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object),
typeof(CommandControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
CommandControl.xaml:
<UserControl x:Class="WpfApp.UserControls.CommandControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Background="Transparent">
</UserControl>
Usage:
<ListBoxItem>
<uc:CommandControl Command="{Binding LoadPageCommand}"
CommandParameter="{Binding HomePageViewModel}">
<TextBlock Text="Home" Margin="0,0,0,5" VerticalAlignment="Center"
Foreground="White" FontSize="24" />
</uc:CommandControl>
</ListBoxItem>
The Content can be whatever, and when the control is clicked, it will execute the command.
EDIT: Added Background="Transparent" to UserControl to enable click events on the entire area of the control.
This is a bit of a hack, but it works well and allows you to use commands and avoid code behind. This also has the added benefit of not triggering the command when you double-click (or whatever your trigger is) in the empty ScrollView area assuming your ListBoxItems don't fill the entire container.
Basically, just create a DataTemplate for your ListBox that is composed of a TextBlock and bind the width of the TextBlock to the width of the ListBox, set the margins and padding to 0, and disable horizontal scrolling (because the TextBlock will bleed beyond the visible bounds of the ScrollView triggering the horizontal scroll bar otherwise). The only bug I've found is that the command won't fire if the user clicks precisely on the border of the ListBoxItem, which I can live with.
Here is an example:
<ListBox
x:Name="listBox"
Width="400"
Height="150"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ItemsSource="{Binding ItemsSourceProperty}"
SelectedItem="{Binding SelectedItemProperty}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Padding="0"
Margin="0"
Text="{Binding DisplayTextProperty}"
Width="{Binding ElementName=listBox, Path=Width}">
<TextBlock.InputBindings>
<MouseBinding
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.SelectProjectCommand}"
Gesture="LeftDoubleClick" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I recently needed to trigger an ICommand upon double clicking a ListBoxItem as well.
Personally, I don't like the DataTemplate method as it is binding to the content inside the ListBoxItem container, and not the container itself. I've opted to use an Attached Property to assign an InputBinding on the container. It takes a little more elbow grease, but it works well.
First, we need to create an attached property class. I've created mine a little more generically towards any class that derives from FrameworkElement, just in case I run into this again with a different visual.
public class FrameworkElementAttachedProperties : DependencyObject
{
public static readonly DependencyProperty DoubleClickProperty = DependencyProperty.RegisterAttached("DoubleClick", typeof(InputBinding),
typeof(FrameworkElementAttachedProperties), new PropertyMetadata(null, OnDoubleClickChanged));
public static void SetDoubleClick(FrameworkElement element, InputBinding value)
{
element.SetValue(DoubleClickProperty, value);
}
public static InputBinding GetDoubleClick(FrameworkElement element)
{
return (InputBinding)element.GetValue(DoubleClickProperty);
}
private static void OnDoubleClickChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = obj as FrameworkElement;
/// Potentially throw an exception if an object is not a FrameworkElement (is null).
if(e.NewValue != null)
{
element.InputBindings.Add(e.NewValue as InputBinding);
}
if(e.OldValue != null)
{
element.InputBindings.Remove(e.OldValue as InputBinding);
}
}
}
Then the final step is to override the base container style for the ListBoxItem.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}"
BasedOn="{StaticResource ListBoxItem}">
<Setter Property="local:FrameworkElementAttachedProperties.DoubleClick">
<Setter.Value>
<MouseBinding Command="{Binding OnListBoxItemDoubleClickCommand}"
MouseAction="LeftDoubleClick"/>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
Now, anytime a ListBoxItem is double clicked, it will fire our OnListBoxItemDoubleClickCommand.
If you're looking for a nice simple solution that uses interactions instead of mucking about with user controls, code behind, input bindings, custom attached properties, etc.
And you want something that works at the ListBoxItem level, i.e. not ListBox level as per the (incorrectly) accepted solution.
Then here's a snippet for a simple 'button like' click action..
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent">
<!-- insert your visuals here -->
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseUp">
<b:InvokeCommandAction Command="{Binding YourCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note, background="Transparent" is required to ensure the entire Grid is clickable and not just the contents inside.