During programming, I ran into the following questions:
-
Does a observable collection implement a CollectionChanged event by itself? (Because of differentbooks refering to the fact that it does, but google shows otherwise)
-
I have the following code, and I want my UI to update by binding (the code is for windowsPhone 7.1) Also, the binding works for single items in my observable collection, but when I try to add a new object to my collection, the CollectionChanged event doesn’t fire.
namespace Phone.lib.ViewModel
{
public class DeviceViewModel : ViewModelBase
{DeviceModelInfo InfoList = new DeviceModelInfo(); public DeviceViewModel() { } public DeviceViewModel(int index) { // Here I add a new item to the collection, But the ui only shows: Beckhoff, ver.... InfoList.Add(new DeviceInfo("name1", "name2", "name3")); } } public class DeviceModelInfo : ObservableCollection<DeviceInfo> { public DeviceModelInfo() : base() { Add(new DeviceInfo("Beckhoff", "Ver. 1A2B3C", "Stopped")); } } public class DeviceInfo : ViewModelBase { private string devicename; private string deviceid; private string devicestatus; public DeviceInfo(string first, string second, string third) { devicename = first; deviceid = second; devicestatus = third; } public string DeviceName { get { return devicename; } set { devicename = value; RaisePropertyChanged("DeviceName"); } } public string DeviceID { get { return deviceid; } set { deviceid = value; } } public string DeviceStatus { get { return devicestatus; } set { devicestatus = value; } } }
Note: The class inherits from viewmodel base wich has the Inotify changed interface in it.
Code from my Xaml:
<phone:PhoneApplicationPage
x:Class="WindowsPhone.View.Device_Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModel="clr-namespace:Phone.lib.ViewModel;assembly=Phone.lib"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True">
<!-- Static Resource area for binding -->
<phone:PhoneApplicationPage.Resources>
<ViewModel:DeviceModelInfo x:Key="deviceinfo"></ViewModel:DeviceModelInfo>
<ViewModel:DeviceModelSensor x:Key="devicesensors"></ViewModel:DeviceModelSensor>
<ViewModel:DeviceModelActuator x:Key="deviceactuators"></ViewModel:DeviceModelActuator>
</phone:PhoneApplicationPage.Resources>
<!-- LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<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="Kremer app" Style="{StaticResource PhoneTextNormalStyle}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Height="100" HorizontalAlignment="Left" Margin="-4,6,0,0" Name="Device_ListBox" VerticalAlignment="Top" Width="460" ItemsSource="{Binding Source={StaticResource deviceinfo}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Height="100">
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextExtraLargeStyle}" Text="{Binding Path=DeviceName, Mode=TwoWay}" />
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Path=DeviceID, Mode=TwoWay}" />
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Path=DeviceStatus, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Height="261" HorizontalAlignment="Left" Margin="-4,138,0,0" Name="Sensor_ListBox" VerticalAlignment="Top" Width="460" ItemsSource="{Binding Source={StaticResource devicesensors}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Height="78">
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextExtraLargeStyle}" Text="{Binding Path=SensorName}" />
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Path=SensorType}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Height="261" HorizontalAlignment="Left" Margin="-4,429,0,0" Name="Actuator_ListBox" ItemsSource="{Binding Source={StaticResource deviceactuators}}" VerticalAlignment="Top" Width="460">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="78" Margin="0,0,0,17" Width="432">
<TextBlock Margin="12,-6,12,0" Style="{StaticResource PhoneTextExtraLargeStyle}" Text="{Binding Path=ActuatorName}" TextWrapping="Wrap" />
<TextBlock Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Path=ActuatorType}" TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
I hope someone is able to help me with this problem because i have been at this for like two days now.
Also, my apologies for my “bad” english, but english is not my native language
Cheers -Bart
EDIT: done a little test
I have run a little debugtest myself to see if the add operation adds to the right collection en therefor increments the count value
public DeviceViewModel(int index)
{
// Here I add a new item to the collection, But the ui only shows: Beckhoff, ver....
Debug.WriteLine("number of added items " + InfoList.Count.ToString());
InfoList.Add(new DeviceInfo("1", "2", "3"));
Debug.WriteLine("number of added items " + InfoList.Count.ToString());
InfoList.Add(new DeviceInfo("1", "2", "3"));
InfoList.Add(new DeviceInfo("1", "2", "3"));
InfoList.Add(new DeviceInfo("1", "2", "3"));
Debug.WriteLine("number of added items " + InfoList.Count.ToString());
}
output:
number of added items 1
number of added items 2
number of added items 5
Edit 2 (19-03-2012)
Last friday I tried to get it working like you suggested. But somehow the XAML can’t find InfoList, and I don’t know why. Maybe I do something wrong in the XAML itself or in the code behind or in the DeviceViewModel. So here is what I have at the moment:
DeviceViewModel:
namespace Phone.lib.ViewModel
{
public class DeviceViewModel : ViewModelBase
{
public DeviceModelInfo InfoList = new DeviceModelInfo();
public DeviceViewModel()
{
//DeviceModelInfo InfoList = new DeviceModelInfo();
InfoList.Add(new DeviceInfo("1", "2", "3"));
}
public DeviceViewModel(int index)
{
}
}
public class DeviceModelInfo : ObservableCollection<DeviceInfo>
{
public DeviceModelInfo() : base()
{
Add(new DeviceInfo("Beckhoff", "Ver. 1A2B3C", "Stopped"));
//this.CollectionChanged += (e, s) => { Debug.WriteLine("event Fired " + e.ToString()); };
}
}
public class DeviceInfo : ViewModelBase
{
private string devicename;
private string deviceid;
private string devicestatus;
public DeviceInfo(string first, string second, string third)
{
devicename = first;
deviceid = second;
devicestatus = third;
}
public string DeviceName
{
get { return devicename; }
set
{
devicename = value;
RaisePropertyChanged("DeviceName");
}
}
public string DeviceID
{
get { return deviceid; }
set { deviceid = value; }
}
public string DeviceStatus
{
get { return devicestatus; }
set { devicestatus = value; }
}
}
The code behind the page:
namespace WindowsPhone.View
{
public partial class Device_Page : PhoneApplicationPage
{
private DeviceViewModel _DV;
public Device_Page()
{
InitializeComponent();
_DV = new DeviceViewModel();
DataContext = _DV;
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
string selectedIndex = "";
if (NavigationContext.QueryString.TryGetValue("selectedItem", out selectedIndex))
{
int index = int.Parse(selectedIndex);
//_DV = new DeviceViewModel(index);
//DataContext = _DV;
Debug.WriteLine("index:" + index.ToString());
}
}
}
}
The XAML code:
<phone:PhoneApplicationPage
x:Class="WindowsPhone.View.Device_Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModel="clr-namespace:Phone.lib.ViewModel;assembly=Phone.lib"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True">
<!-- Static Resource area for binding -->
<phone:PhoneApplicationPage.Resources>
<ViewModel:DeviceViewModel x:Key="deviceinfo"></ViewModel:DeviceViewModel>
<ViewModel:DeviceModelSensor x:Key="devicesensors"></ViewModel:DeviceModelSensor>
<ViewModel:DeviceModelActuator x:Key="deviceactuators"></ViewModel:DeviceModelActuator>
</phone:PhoneApplicationPage.Resources>
<!-- LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<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="Kremer app" Style="{StaticResource PhoneTextNormalStyle}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Height="100" HorizontalAlignment="Left" Margin="-4,6,0,0" Name="Device_ListBox" VerticalAlignment="Top" Width="460" ItemsSource="{Binding InfoList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Height="100">
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextExtraLargeStyle}" Text="{Binding Path=DeviceName, Mode=TwoWay}" />
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Path=DeviceID, Mode=TwoWay}" />
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Path=DeviceStatus, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Height="261" HorizontalAlignment="Left" Margin="-4,138,0,0" Name="Sensor_ListBox" VerticalAlignment="Top" Width="460" ItemsSource="{Binding Source={StaticResource devicesensors}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Height="78">
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextExtraLargeStyle}" Text="{Binding Path=SensorName}" />
<TextBlock TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Path=SensorType}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Height="261" HorizontalAlignment="Left" Margin="-4,429,0,0" Name="Actuator_ListBox" ItemsSource="{Binding Source={StaticResource deviceactuators}}" VerticalAlignment="Top" Width="460">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="78" Margin="0,0,0,17" Width="432">
<TextBlock Margin="12,-6,12,0" Style="{StaticResource PhoneTextExtraLargeStyle}" Text="{Binding Path=ActuatorName}" TextWrapping="Wrap" />
<TextBlock Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Path=ActuatorType}" TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
1)
ObservableCollectionimplements the INotifyCollectionChanged interface, which defines theCollectionChangedevent.2) When you add a new item in
DeviceViewModelyou do it to a new instance ofDeviceModelInfo, so a different instance than the one you declared in your XAMLYou have to either bind to the
DeviceModelInfoinstance inDeviceViewModelor use the instance of
DeviceViewModel, declared in your XAMLEdit
In your XAML you have
That is the same as typing ‘new DeviceModelInfo()’ and then registering that instance in the resources of your control
PhoneApplicationPage. And you bind the theItemsSourceof yourListBoxto that particular instance.Now in your
DeviceViewModelclass you declareInfoListlike thisYou create a new instance of
DeviceModelInfo, soInfoListis not the same instance/object as the instance/object in your XAML.You must either
1) Bind your
ItemsSourceof theListBoxto the instance you have inDeviceViewModel. To do this you must first exposeInfoList, that is make it public preferably through a property (but that’s just convention, not required). Then make sure theDataContextof your control is set to the instance ofDeviceViewModelyour’re working with. And then you can set the binding like thisAssuming
InfoListis public2) Get the instance
deviceinfocreated in your XAML like this:assuming the instance of your control is called
phoneApplicationPage. If you do it in the code behind of your control thenphoneApplicationPagewould bethis.And now you can pass this instance (
deviceinfo) to your instance ofDeviceViewModel.From the naming I assume you’re attempting to use the MVVM pattern, in which case you should go with 1)
Edit
Making the field public is good enough.
Now you need to bind it to the
ItemsSourceproperty of theListBox. Which can be as simple asBut this requires that the
DataContextproperty of your page (PhoneApplicationPage) is set to an instance ofDeviceViewModel.Without knowing exactly how you currently instantiate
DeviceViewModel, it’s hard for me to explain exactly how you can go about doing this. But I assume you instantiateDeviceViewModelin the code-behind of your page, so it looks something like this:Once you’ve made sure the
DataContextis set to yourDeviceViewModelinstance then you can change the binding in your XAML like stated above.So you should change the line
to