I'm having a look at this MVVM stuff and I'm facing a problem.
The situation is pretty simple.
I have the following code in my index.xaml page
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<view:MovieView ></view:MovieView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and in my index.xaml.cs
...
InitializeComponent();
base.DataContext = new MovieViewModel(ent.Movies.ToList());
....
and here is my MoveViewModel
public class MovieViewModel
{
readonly List<Movies> _m;
public ICommand TestCommand { get; set; }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new TestCommand(this);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
}
finally
here is my control xaml MovieView
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label VerticalAlignment="Center" Grid.Row="0" Grid.Column="0">Title :</Label><TextBlock VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" Text="{Binding Title}"></TextBlock>
<Label VerticalAlignment="Center" Grid.Row="1" Grid.Column="0">Director :</Label><TextBlock VerticalAlignment="Center" Grid.Row="1" Grid.Column="1" Text="{Binding Director}"></TextBlock>
<Button Grid.Row="2" Height="20" Command="{Binding Path=TestCommand}" Content="Edit" Margin="0,4,5,4" VerticalAlignment="Stretch" FontSize="10"/>
</Grid>
So the problem I have is that if I set ItemsSource at Binding
it doesn't make anything
if I set ItemsSource="{Binding lm}"
it populates my itemsControl but the Command (Command="{Binding Path=TestCommand}" ) doesn't not work.
Of course it doesn't not work because TestCommand doesn't not belong to my entity object Movies.
So finally my question is,
what do I need to pass to the ItemsControl to make it working?
Thx in advance
As soon as your items are rendered, each item gets set to the DataContext of the specific row it represents, so you are no longer able to reference to your first DataContext.. Also, due to the fact that you are in a DataTemplate, your bindings will start working when there is need for that Template.. so in that case you need to look up your parent control through a RelativeSource binding...
Hope that explains some things..
Try implementing the INotifyPropertyChanged interface:
public class MovieViewModel : INotifyPropertyChanged
{
readonly List<Movies> _m;
private ICommand _testCommand = null;
public ICommand TestCommand { get { return _testCommand; } set { _testCommand = value; NotifyPropertyChanged("TestCommand"); } }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new TestCommand(this);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
}
}
}
What happens is that the TestCommand has a value, and the UI gets no notification that a change is taking place.. On controls you solve this problem using Dependency properties, on data object, you can use the INotifyPropertyChanged interface..
Secondly, the Movie objects have no reference to the parent object..
You can solve this problem in three different ways:
have a reference back to the model on Movie, and make the Bind path like so: ie.. if you property is named ParentMovieModel, then your Binding will be like:
{Binding Path=ParentMovieModel.TestCommand}
Make a binding based on ElementName like so: Seek up the parent control where you set your DataContext on, and give it a name: i.e. Root. Now create a binding based on the ElementName like so:
{Binding ElementName=Root, Path=DataContext.TextCommand}
Make a binding based on a RelativeSource like so: Seek up the parent control by type, and use the same path as the one above...
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type yourparentwindowtype}}, Path=DataContext.TextCommand}
Got it working
here is the thing
<ItemsControl DataContext="{Binding}" ItemsSource="{Binding lm}">
Command="{Binding Path=DataContext.TestCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
so the RelativeSource was the thing I've missed.
if somebody has a good explaination of this, I would be definitely happy.
//include namespace
using Microsoft.Practices.Composite.Wpf.Commands;
readonly List<Movies> _m;
public ICommand TestCommand { get; set; }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new DelegateCommand<object>(TestcommandHandler);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
void TestcommandHandler(object obj)
{
// add your logic here
}
}
What about <ItemsControl ItemsSource="{Binding Path=lm}">
?
in the ItemsSource="{Binding Path=lm}"> case
the itemsControl works well but I complety bypass my MovieViewModel
and I got this in the output window
System.Windows.Data Error: 39 : BindingExpression path error: 'TestCommand' property not found on 'object' ''Movies' (HashCode=1031007)'. BindingExpression:Path=TestCommand; DataItem='Movies' (HashCode=1031007); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
Movies is my entity object and owns only the Title and Director properties