I am creating up a dashboard type UI for couple of sensors device that are on the field. A timer based event gets the last record from the DB and updates the dashboard UI. I want to dynamically creating my "sensors" which are on BindableCollection and then associated with a ItemsControl on the XAML that contains nested ItemsControl. Using MVVM architecture, and Caliburn Micro for Binding.
Have used full property and make sure I call NotifyOfPropertyChange after updating my BindableColleciton
Edit: Following others threads I have change the System.Timer for a DispatcherTimer() so the update runs on the UI thread. DispatcherTimer vs a regular Timer in WPF app for a task scheduler
Moreover, because my BindableCollection members were always the same I have make sure the collection is recreated at every, as I have the idea "An ObservableCollection will notify the UI when a record is added or removed but not when a record is edited. It's up to the object that has been changed to notify that it has changed."
Properties:
private int _offlineSensors;
public int OfflineSensors
{
get { return _offlineSensors; }
set
{
_offlineSensors = value;
NotifyOfPropertyChange(() => OfflineSensors);
}
}
private int _latchedAlarms;
public int LatchedAlarms
{
get { return _latchedAlarms; }
set
{
_latchedAlarms = value;
NotifyOfPropertyChange(() => LatchedAlarms);
}
}
private int _activeAlarms;
public int ActiveAlarms
{
get { return _activeAlarms; }
set
{
_activeAlarms = value;
NotifyOfPropertyChange(() => ActiveAlarms);
}
}
private Caliburn.Micro.BindableCollection<SensorStatusTable> _sensorStatusFilteredDash;
public Caliburn.Micro.BindableCollection<SensorStatusTable> SensorStatusFilteredDash
{
get { return _sensorStatusFilteredDash; }
set
{
_sensorStatusFilteredDash = value;
NotifyOfPropertyChange(() => SensorStatusFilteredDash);
}
}
XAML:
<ItemsControl Foreground="Black" Background="Black" ItemsSource="{Binding Path=SensorStatusFilteredDash, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="RoyalBlue" BorderThickness="2" Margin="2" Padding="5">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontSize="20" />
<Image MaxWidth="60" MaxHeight="60" Source="{Binding CardStatusImage, Converter={StaticResource ResourceKey = ImageConverter}, UpdateSourceTrigger=PropertyChanged}" Stretch="Uniform"></Image>
<ItemsControl Foreground="Black" Background="Black" ItemsSource="{Binding Path=SensorTypes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }" TextWrapping="WrapWithOverflow" FontWeight="Bold" FontSize="12" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" Margin="25.5,1,18,1"></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Foreground="Black" Background="Black" ItemsSource="{Binding Path=InternalSensorStatusImages, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Margin="11.5,1" MaxWidth="40" HorizontalAlignment="Center" VerticalAlignment="Center" MaxHeight="40" Source="{Binding Converter={StaticResource ResourceKey = ImageConverter}}" Stretch="Uniform"></Image>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Foreground="Black" Background="Black" ItemsSource="{Binding Path=InternalSensorStatusDescription, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="WrapWithOverflow" FontWeight="Bold" FontSize="12" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" Margin="10,1,10,1"></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Event (Edited):
var conn = GlobalConfig.Connections.FirstOrDefault(c => c.GetType() == typeof(SQLConnector));
if (conn == null) return;
try
{
ActiveAlarms = conn.GetNumberActiveAlarms(0);
LatchedAlarms = conn.GetNumberActiveAlarms(2);
GasSensors = new Caliburn.Micro.BindableCollection<GasSensor>(conn.GetGasSensors());
SensorStatusDash = new Caliburn.Micro.BindableCollection<SensorStatusTable>(conn.SensorStatusDashboard());
_sensorStatusFilteredDash.Clear();
_sensorStatusFilteredDash = new Caliburn.Micro.BindableCollection<SensorStatusTable>(StatusImageConverter());
NotifyOfPropertyChange(() => SensorStatusFilteredDash);
NotifyOfPropertyChange(() => OfflineSensors);
NotifyOfPropertyChange(() => ActiveAlarms);
NotifyOfPropertyChange(() => LatchedAlarms);
}
catch (Exception ex)
{
_logger.Error(ex, "Error - Refreshing Dashboard Status");
}
This is the first view / view model of my application, the UI refresh event as above always work until I have focus a different view and return to it. The timer is def still being triggered and placing the right state on the BindableCollection. Also, even the properties that simple Int do not update. Putting a break point a the property setter I can see it never hits its like NotifyOfPropertyChange event is not triggered. Any idea?
Thanks everyone for the tips and help.
I have made it work, there was a couple of things that were causing the UI not be refreshed.
It was a combination of all the 3 points above.