I'm trying to bind List<> to ListView. When i'm updating i'm going to clear the list. Clear on ObservableCollection was kind of slow.
Problem is that in the view things are not updated correctly.
XAML
<StackPanel.Resources>
<ResourceDictionary>
<common:BoolToBackgroundConverter x:Key="BoolToBackground"/>
<tb:StringInlineCollectionConvertor x:Key="InlineConvert"/>
</ResourceDictionary>
</StackPanel.Resources>
<ListView ItemsSource="{Binding NotificationsCollection, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
ScrollViewer.CanContentScroll="False">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<Grid Background="{Binding NotSeen,Converter={StaticResource BoolToBackground},UpdateSourceTrigger=PropertyChanged,NotifyOnSourceUpdated=True}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Thumb}"/>
<tb:BindableTextBlock InlineCollection="{Binding Text, Converter={StaticResource InlineConvert}}"/>
<TextBlock Text="{Binding Created}"/>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C#
ViewModel
public List<NotificationDataModel> notificationsCollection;
public List<NotificationDataModel> NotificationsCollection
{
get
{
if (notificationsCollection == null)
{
notificationsCollection = new List<NotificationDataModel>();
}
return notificationsCollection;
}
set
{
if (notificationsCollection == null)
{
notificationsCollection = new List<NotificationDataModel>();
}
notificationsCollection.Clear();
foreach (var item in value)
{
notificationsCollection.Add(item);
}
this.OnPropertyChanged("NotificationsCollection");
}
}
public void UpdateNotifications1()
{
List<NotificationDataModel> newCollection = new List<NotificationDataModel>();
newCollection.Add(item1);
newCollection.Add(item2);
newCollection.Add(item3);
newCollection.Add(item4);
newCollection.Add(item5);
newCollection.Add(item6);
this.NotificationsCollection = newCollection;
}
public void UpdateNotifications2()
{
List<NotificationDataModel> newCollection = new List<NotificationDataModel>();
newCollection.Add(item1);
newCollection.Add(item2);
newCollection.Add(item6);
this.NotificationsCollection = newCollection;
}
When i call UpdateNotifications1 elements are show accordingly but after that when i call UpdateNotifications2 i see item1, item2 and item3 instead of item6.
Also items are getting new value for the NotSeen(black for example, and initial is white) property after closing the view and on reopening those items should have white but they are still with black background.
The problem is that raising the PropertyChanged event in your NotificationsCollection setter is ineffective when the actual value of the underlying field hasn't changed. The target ItemsSource property receives the same List instance (from the binding) and therefore does not trigger a UI update.
So do not clear and copy to an existing collection, but use the one passed to the setter instead:
public List<NotificationDataModel> NotificationsCollection
{
get { return notificationsCollection; }
set
{
notificationsCollection = value;
OnPropertyChanged("NotificationsCollection");
}
}
well either create a new list every time or use something like ObservableCollection<>
Related
Okay, sorry for my previous mess.
The situation is this:
I have two custom objects defined as follows:
MainObject:
public class MainObject
{
private string mainObjectName;
public string MainObjectName { get { return mainObjectName; } }
private List<SubObject> subObjectData;
public List<SubObject> SubObjectData { get { return subObjectData; } }
public MainObject(string name, List<SubObject> objectData)
{
mainObjectName = name;
subObjectData = objectData;
}
}
SubObject:
public class SubObject
{
private string subObjectName;
public string SubObjectName { get { return subObjectName; } }
private List<int> integerData;
public List<int> IntegerData { get { return integerData; } }
public SubObject(string name, List<int> data)
{
subObjectName = name;
integerData = data;
}
}
I also have a viewmodel which for simplicity defines some data using those two objects as follows:VM
public List<Model.MainObject> VMList = new List<Model.MainObject>()
{
new Model.MainObject("MainItem1", new List<Model.SubObject>()
{
new Model.SubObject("SubItem1", new List<int>() { 1,6,3}),
new Model.SubObject("SubItem2", new List<int>() { 5,2,9})
}),
new Model.MainObject("MainItem2", new List<Model.SubObject>()
{
new Model.SubObject("SubItem1", new List<int>() { 0,3,1}),
new Model.SubObject("SubItem2", new List<int>() { 7,5,2})
})
};
now I have the following UI
<Grid>
<ItemsControl Name="MainObjectIC">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MainObjectName}"/>
<ItemsControl Name="SubObjectIC">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubObjectName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
I assign the ItemsSource of the MainObjectIC in the code behind like so:
ViewModel.VM dc = new ViewModel.VM();
public MainWindow()
{
InitializeComponent();
DataContext = dc;
MainObjectIC.ItemsSource = dc.VMList;
}
I also want to assign the ItemsSource to the SubObjectIC but to do that I have to get that ItemsControl object. And this is what I am trying to achieve.
From what I understood it may be a very very bad and useless to assign the ItemsSource property from the code behind.
Thank you for improving your code example. It still wasn't quite complete, but it's close enough to be able to provide an answer.
In your example, the main thing missing is to simply add the necessary {Binding} expression. In particular:
<ItemsControl Name="SubObjectIC" Grid.Column="1"
ItemsSource="{Binding SubObjectData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubObjectName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The context for the item already is an object of type MainObject (which is why your TextBlock binding works). So all that remains to be done is bind the ItemsSource property to the MainObject.SubObjectData property.
(I had to add the Grid.Column assignment, which appeared to be missing from your example above.)
The above change is completely sufficient to get your example to work as you desire. However, you can also improve the code by using the same basic approach to the top-level control as well. To do so, your VM.VMList field needs to be changed to be a property (WPF only binds to properties, not fields):
class VM
{
public List<MainObject> VMList { get { return _vmList; } }
private readonly List<MainObject> _vmList = new List<MainObject>()
{
new MainObject("MainItem1", new List<SubObject>()
{
new SubObject("SubItem1", new List<int>() { 1,6,3}),
new SubObject("SubItem2", new List<int>() { 5,2,9})
}),
new MainObject("MainItem2", new List<SubObject>()
{
new SubObject("SubItem1", new List<int>() { 0,3,1}),
new SubObject("SubItem2", new List<int>() { 7,5,2})
})
};
}
Then you can just remove the explicit assignment that's in your constructor:
public MainWindow()
{
InitializeComponent();
DataContext = dc;
}
With those changes, your XAML no longer needs to give any of the controls names, and you can bind directly to the relevant properties:
<Window x:Class="TestSO42929995WpfNestedData.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:TestSO42929995WpfNestedData"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding VMList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MainObjectName}"/>
<ItemsControl Grid.Column="1"
ItemsSource="{Binding SubObjectData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubObjectName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
A key point that might not be obvious in the above is that every control has a DataContext. When you using the {Binding} syntax, by default the property path is relative to that context. In the top-level control, the context is what you set it to be in the constructor. But in the individual list item template, the context is the individual data object for that list item, which in your case is the MainObject object. So in that context, you just bind to the SubObjectData property, just like you bind to the MainObjectName. It works exactly the same, and for the same reason.
I have a scenario where the same collection of items can be viewed in different ways. That is, we have multiple visual representations for the same data. In order to keep our application visually clean you can only view one of these views at a time. The problem I'm having is that if you change the selected item while viewing View #1 then when you switch to View #2 the selected item isn't updating properly.
My steps for reproducing:
On View #1 select Item #1.
Toggle to View #2 - at this point Item #1 is selected
Scroll down to "Item #200" and select it
Toggle back to View #1
Item #1 will still be highlighted and if you scroll down to Item #200 it is also highlighted
It seems like when the listbox is collapsed the selection changes aren't being picked up. What am I missing? Is it expected that the PropertyChanged events won't update the UI elements if they aren't visible?
I have a very simplified version of my code below. Basically, I have a shared array that is being bound to two different ListBox controls.
XAML:
<Window x:Class="SharedListBindingExample.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:SharedListBindingExample"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="listBox1" ItemsSource="{Binding List1}">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
<DataTemplate DataType="{x:Type local:SharedListItem}">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Background="Red" />
<Label Grid.Row="1" Content="{Binding Name}" />
</Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
<ListBox Grid.Row="0" x:Name="listBox2" ItemsSource="{Binding List2}" Background="AliceBlue" Visibility="Collapsed">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
<DataTemplate DataType="{x:Type local:SharedListItem}">
<Label Content="{Binding Name}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"/>
</DataTemplate>
</ListBox.Resources>
</ListBox>
<Button Grid.Row="1" Click="Button_Click">Toggle View</Button>
</Grid>
</Window>
Code Behind:
using System.Windows;
namespace SharedListBindingExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (listBox1.Visibility == Visibility.Collapsed)
{
listBox1.Visibility = Visibility.Visible;
listBox2.Visibility = Visibility.Collapsed;
}
else
{
listBox2.Visibility = Visibility.Visible;
listBox1.Visibility = Visibility.Collapsed;
}
}
}
}
ViewModel:
using System.Collections.Generic;
namespace SharedListBindingExample
{
public class TwoPropertiesForSameListViewModel
{
private readonly List<SharedListItem> _sharedList;
public TwoPropertiesForSameListViewModel()
{
_sharedList = new List<SharedListItem>();
for (int i = 0; i < 300; i++)
{
_sharedList.Add(new SharedListItem($"Item #{i}"));
}
}
public IEnumerable<SharedListItem> List1
{
get
{
return _sharedList;
}
}
public IEnumerable<SharedListItem> List2
{
get
{
return _sharedList;
}
}
}
}
SharedListItem:
using System.ComponentModel;
namespace SharedListBindingExample
{
public class SharedListItem : INotifyPropertyChanged
{
private bool _isSelected;
public SharedListItem(string name)
{
Name = name;
}
public string Name { get; set; }
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
if (value != _isSelected)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I believe you need to use a CollectionViewSource object between the two different views to keep the selected item in sync. I do something similar in my own application.
I have one control which defines a CollectionViewSource resource in the Xaml:
<UserControl.Resources>
<CollectionViewSource x:Key="DataView" />
</UserControlResources>
The control also has a DependencyProperty for the CollectionViewSource that allows it to be Data Bound to other controls:
public static readonly DataViewProperty =
DependencyProperty.Register( "DataView", typeof( CollectionViewSource ), typeof( YourControlType ), new PropertyMetadata( null ) );
public CollectionViewSource DataView {
get { return (CollectionViewSource) GetProperty( DataViewProperty); }
set { SetProperty( DataViewProperty, value );
}
Then in the components constructor, after calling InitializeComponent, you have to execute code like this:
public MyUserControl() {
InitializeComponent();
DataView = FindResource( "DataView" ) as CollectionViewSource;
DataView.Source = YourObservableCollection;
}
In the other view(s) where you want to share this object, you create a new CollectionViewSource DependencyProperty. This allows you to bind the two proeprties to each other in the window that has the different views of your data. In my second control, I have another ObservableCollection object property, but it is not initialized in the control's constructor. What I do is in the control's Loaded event handler, I set that ObservableCollection property's value to the value of the CollectionViewSource object's Source property. That is:
if ( DataCollection == null && DataView != null ) {
DataCollection = (ObservableCollection<DataType>) DataView.Source;
DataGrid.ItemsSource = DataView.View;
}
After this, both controls share the same ObservableCollection and the same CollectionViewSource. It's the CollectionViewSource that keeps the two control's selected item in sync.
Obviously, you can share that CollectionViewSource object across as many views as you like. One control has to declare the object, the others have to share it.
When using WPF, it is recommended to ditch IEnumerables for ObservableCollection or ICollectionView. You can find more details about these collections on MSDN, but my suggestion is to bind SelectedItem of one ListBox to another in XAML (by SelectedItem = {Binding ElementName='yourList', Path='SelectedItem'}), so that when one changes, the other one will respond (you should do this for both lists). I have never tried this cyclic binding myself, but I think it will work fine in your case.
I have the following ListView:
<ListView
Grid.Row="1"
ItemsSource="{Binding MeditationDiary}" >
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="{Binding ElementName=ListViewHeaders, Path=ActualWidth}" >
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding StartTime}" />
<TextBlock
Grid.Column="1"
Text="{Binding TimeMeditated}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is bound to a MeditationDiary property of type MeditationDiary consisting of MeditationEntries:
public class MeditationDiary : Collection<MeditationEntry> { }
public class MeditationEntry
{
public DateTime StartTime { get; set; }
public TimeSpan TimeMeditated { get; set; }
}
The ListView binds to this MeditationDiary:
private MeditationDiary _meditationDiary;
public MeditationDiary MeditationDiary
{
get
{
RaisePropertyChanged(nameof(MeditationDiary));
return _meditationDiary;
}
set
{
_meditationDiary = value;
RaisePropertyChanged(nameof(MeditationDiary));
}
}
Strangely enough when I assign a new MeditationDiary object to the property (which contains data with MeditationEntries) the ListView does no longer display data.
I'm assigning the new MeditationDiary object in the UpdateDiary method which is called after adding an entry:
private async void UpdateDiary()
{
var latestDiary = await _repository.GetAsync();
MeditationDiary = latestDiary;
}
Why can this be and how can it be fixed?
This is most likely the culprit that's messing up your binding:
MeditationDiary = latestDiary;
Instead, try clearing out the current collection and then adding the new values to it:
MeditationDiary.Clear();
foreach (var entry in latestDiary)
MeditationDiary.Add(entry);
You'll probably have to call RaisePropertyChanged on the collection after you add the new items.
As a side note, you could replace MeditationDiary with an ObservableCollection<MeditationEntry>, which automatically notifies the UI when you add/remove items in it.
I have a problem that resists the past few hours, here is the ViewModel code: (PS: I can not share the url stream but do not worry its march because I tested it with BreakPoint)
private ObservableCollection<CustomerPublic> customers;
List<CustomerPublic> liste = new List<CustomerPublic>();
public ObservableCollection<CustomerPublic> Customers
{
get
{ return customers; }
set
{
if (customers != value)
{
customers = value;
RaisePropertyChanged("Customers");
}
}
}
private int id;
public int ID
{
get
{
return id;
}
set
{
id = value;
RaisePropertyChanged("ID");
}
}
public Detail_AgenceViewModel(int id)
{
this.ID = id;
PopulateCollection();
}
public Detail_AgenceViewModel()
{
}
private void PopulateCollection()
{
ParseFeedRequest();
}
private void ParseFeedRequest()
{
RestClient client = new RestClient();
client.BaseUrl = "....";
RestRequest request = new RestRequest();
.......
client.ExecuteAsync(request, ParseFeedCallBack);
}
public void ParseFeedCallBack(IRestResponse response)
{
if (response.StatusCode == HttpStatusCode.OK)
{
ParseXMLFeed(response.Content);
}
}
private void ParseXMLFeed(string feed)
{
if (feed == null)
return;
XElement xmlItems = XElement.Parse(feed);
liste = (from response in xmlItems.Descendants("result")
let lib = response.Element("lib")
let adresse = response.Element("adresse")
select new CustomerPublic
{
lib = lib == null ? null : lib.Value,
adresse = adresse == null ? null : adresse.Value,
}).ToList();
Customers = new ObservableCollection<CustomerPublic>(liste);
}
the View:
<phone:PhoneApplicationPage.DataContext>
<vm:Detail_AgenceViewModel/>
</phone:PhoneApplicationPage.DataContext>
<Grid x:Name="LayoutRoot"
Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel"
Grid.Row="0"
Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle"
Text="MY APPLICATION"
Style="{StaticResource PhoneTextNormalStyle}" />
<TextBlock x:Name="PageTitle"
Text="page name"
Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle1Style}" />
</StackPanel>
<!--ContentPanel - place additional content here-->
<StackPanel x:Name="ContentPanel" Grid.Row="2" Margin="12,0,12,0" Orientation="Vertical">
<!--TextBox Text="{Binding Count, Mode=TwoWay}" x:Name="tbCount" />
<TextBlock Text="{Binding Count}" /-->
<ListBox x:Name="Agences" ItemsSource="{Binding Customers}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding lib}" />
<TextBlock Text="{Binding adresse}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
The problem is that its all going well Customers even she is loaded but nothing appears! someone has an idea?
You are setting Customers to a new instance of an observable collection!
When you change the observable collection to a new instance, you must use INotifyPropertyChanged to tell the view that the collection has changed to a new instance - although changes to the items IN the collection are notified, changes to the collection ITSELF are not.
When you do this:
Customers = new ObservableCollection<CustomerPublic>(liste);
The view is still bound to the OLD collection. You should do:
Customers.Clear();
foreach(var item in liste)
Customers.Add(item);
OR make sure that the Customers property calls the NotifyPropertyChanged function.
Have a check of this video or article for more info:
http://www.codeproject.com/Articles/371217/Apex-Part-1-Create-Your-First-MVVM-Application
http://www.youtube.com/watch?v=m4cx9w5fiwk&feature=youtu.be
Good luck and please do let me know if this helps!!
I'm having similar problems ;
public void FillList(List<StockItem> siList)
{
listBox.ItemsSource = siList;
}
Where sIList is a filled list of X items, with correctly named properties.
Program builds & runs fine, but the listbox isnt shown. (this problem started when transitioning into MVVM)
I've got it.
Check your datacontext - I bet it is null. I've had this exact same issue in WP7. In the constructor of your PhoneApplicationPage do:
DataContext = new Detail_AgenceViewModel();
and initialise it there. In WP7 when I create the datacontext in XAML it's null. Does this help?
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