Dynamically add eventtocommand actions to a listbox
I have a page with
namespaces:
xmlns:Custom="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7"
xaml at design time:
<ListBox x:Name="ItemGroupsList" ItemsSource="{Binding ItemGroups}" Height="496"
SelectedItem="{Binding SelectedItemGroup, Mode=TwoWay}" >
<Custom:Interaction.Triggers>
<Custom:EventTrigger EventName="SelectionChanged">
<GalaSoft_MvvmLight_Command:EventToCommand
x:Name="SelectionChangedEvent开发者_运维知识库"
Command="{Binding GoToEditItemGroupCommand, Mode=OneWay}"
PassEventArgsToCommand="True"/>
</Custom:EventTrigger>
</Custom:Interaction.Triggers>
In the code, I am generating multiple listboxes at run time and would like to be able to bind to a relaycommand on the viewmode like the above xaml code shown above...
How do I do the above at run time in the code behind of the view.
Also, I would like to assign the datacontext for the dynamically generated listboxes to different view models than the one currently bound to my view.
Basically, I have a panaroma and with each panaroma items being created dynamically with each panaroma item having a listbox that will be bound to a viewmodel with relaycommands
Note: See also related post.
This article describes how you can attach behaviours from code behind.
However, I firmly would not advise you to go down this route unless you have a compelling need to do so. If you use this approach in your ViewModel
, you will loose all testability as it generates a ViewModel
that is highly coupled to the View
, and in fact cannot live without it. (Side note: for this reason it is also not good practise to return the event args to the ViewModel
use CommandParameter
instead to return the DataContext
if needed).
Normally you can archive your goal using MVVM in another manner, and the rest of the post describes this.
First, you do not need to use a Command
to get the selected property, nor to get the notification that this property has changed. The usual pattern for this is that you bind the SelectedItem
of your list box to a property in your ViewModel
. Now you can use the PropertyChanged
event to track when this property changed.
Second, use templates to generate and style your listboxes. When you need to only show them when the item is selected use a BooleanToVisibility
(Sample see here converter and bind the sub-listboxes' Visibility
property to a property on your ViewModel
using the converter (not my the sample).
The sample creates a ListBox that has its ItemsSource
bound to the Items property of the ViewModel
. It further creates a ContentControl
that has its DataContext
bound to the SelectedItem
of the ViewModel
. The ContentControl
then contains again a ListBox
where the DataContext
is bound to the SubItem
property of the ItemViewModel
. The MainViewModel
generates test data to be shown at design time and run time.
The rest of the post shows an sample:
1. XAML of main page (except):
<ContentControl x:Name="target1" Grid.Row="1" DataContext="{Binding SelectedItem}" Margin="20,0">
<ContentControl.Template>
<ControlTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
<ListBox ItemsSource="{Binding SubItems}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="navy" BorderBrush="White" BorderThickness="1">
<TextBlock Text="{Binding Name}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</Grid>
</Grid>
2. The Main View Model
public MainViewModel()
{
// working with fields to ensure no events are fired during initial phase
this._items = new ObservableCollection<ItemViewModel>();
for (int i = 0; i < 5; ++i) {
var item = new ItemViewModel() { Name = string.Format("Item {0}", i) };
for (int j = 0; j < 3; ++j)
item.SubItems.Add(new ItemViewModel() { Name = string.Format("{0} - Sub Item {1}", item.Name, j) });
this._items.Add(item);
}
this.SelectedItem = this._items[0];
if (IsInDesignMode) {
// Code runs in Blend --> create design time data.
} else {
// Code runs "for real"
}
}
#region [Items]
/// <summary>
/// The <see cref="Items" /> property's name.
/// </summary>
public const string ItemsPropertyName = "Items";
private ObservableCollection<ItemViewModel> _items = default(ObservableCollection<ItemViewModel>);
/// <summary>
/// Gets the Items property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public ObservableCollection<ItemViewModel> Items {
get {
return _items;
}
set {
if (_items == value) {
return;
}
var oldValue = _items;
_items = value;
// Update bindings, no broadcast
RaisePropertyChanged(ItemsPropertyName);
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
//RaisePropertyChanged(ItemsPropertyName, oldValue, value, true);
}
}
#endregion
#region [SelectedItem]
/// <summary>
/// The <see cref="SelectedItem" /> property's name.
/// </summary>
public const string SelectedItemPropertyName = "SelectedItem";
private ItemViewModel _selectedItem = default(ItemViewModel);
/// <summary>
/// Gets the SelectedItem property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public ItemViewModel SelectedItem {
get {
return _selectedItem;
}
set {
if (_selectedItem == value) {
return;
}
var oldValue = _selectedItem;
_selectedItem = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedItemPropertyName);
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
//RaisePropertyChanged(SelectedItemPropertyName, oldValue, value, true);
}
}
#endregion
}
3. The ItemView Model
public class ItemViewModel : ViewModelBase { #region [Name]
/// <summary>
/// The <see cref="Name" /> property's name.
/// </summary>
public const string NamePropertyName = "Name";
private string _name = default(string);
/// <summary>
/// Gets the Name property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public string Name {
get {
return _name;
}
set {
if (_name == value) {
return;
}
var oldValue = _name;
_name = value;
// Update bindings, no broadcast
RaisePropertyChanged(NamePropertyName);
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
//RaisePropertyChanged(NamePropertyName, oldValue, value, true);
}
}
#endregion
#region [SubItems]
/// <summary>
/// The <see cref="SubItems" /> property's name.
/// </summary>
public const string SubItemsPropertyName = "SubItems";
private ObservableCollection<ItemViewModel> _myProperty = new ObservableCollection<ItemViewModel>();
/// <summary>
/// Gets the SubItems property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public ObservableCollection<ItemViewModel> SubItems {
get {
return _myProperty;
}
set {
if (_myProperty == value) {
return;
}
var oldValue = _myProperty;
_myProperty = value;
// Update bindings, no broadcast
RaisePropertyChanged(SubItemsPropertyName);
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
//RaisePropertyChanged(SubItemsPropertyName, oldValue, value, true);
}
}
#endregion
}
Edit 2
The templating of the Panorama
control can be found here.
精彩评论