2

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?

XAMlMAX
  • 2,268
  • 1
  • 14
  • 23
  • I am almost sure its something regarding the fact that my timer is on different thread, the only thread allowed to make changes to UI is the main thread. Putting a Breakpoints there the information inside the BindableCollection is correct. Its just its not getting raised to the UI, how from inside the timer can I make sure I raise the Notify Property to the Main Thread? – Daniel Ferreira Jul 17 '19 at 23:44
  • Did you verify that the connection was return data and was not null? – jdweng Jul 17 '19 at 23:46
  • I did, the data inside the BindableCollection item is on the correct state, it represents the correct state the UI shall be. Its just that the UI is not updated with the correct details. – Daniel Ferreira Jul 18 '19 at 00:01
  • I am bit lost, as no exception is catch on the try catch block, or it only happens randomly. Sometimes the UI refresh with the correct details, other it doesn't, however, the BindableCollection does have the correct details its somewhere on the binding or the NotifyProperyChanged. – Daniel Ferreira Jul 18 '19 at 00:08
  • I may just be the view is not getting re-painted. – jdweng Jul 18 '19 at 00:55
  • Yeah, the problem is the view not refresh, the details on the UI do not update with the BindableCollection update. Or at least they do not update all the times each is even more confusing, it works sometimes. – Daniel Ferreira Jul 18 '19 at 01:46
  • Following this https://stackoverflow.com/questions/2258830/dispatchertimer-vs-a-regular-timer-in-wpf-app-for-a-task-scheduler/2258909#2258909 I have replaced the timer for a dispatcher time. Now the problem only occurs when I had navigated through other UserControl and then get back to this page. – Daniel Ferreira Jul 18 '19 at 04:35
  • Before leaving the view or page unsubscribe from the timer event (much better: stop the timer!) and register (start timer) again on page loaded – BionicCode Jul 18 '19 at 06:34
  • On the ctor I am checking if the dispatchertimer is already running (case the page was active previously). If yes I do stop it, and restart it. I am a bit lost how can I detect I have swapped pages. As I have a shell view model, that has 4 buttons that can call others views that also have their owns tabs. – Daniel Ferreira Jul 18 '19 at 07:03
  • Be aware that a new Page instance may be created on back navigation. – Clemens Jul 18 '19 at 07:27
  • Get the data in your background thread, raise event on `UI` thread! To raise those events use Dispatcher! What happened to an `ObservableCollection`? – XAMlMAX Jul 18 '19 at 07:57

1 Answers1

0

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.

  1. As mentioned above, I was using a Timer from System.Timer. I have replaced it with a DispatcherTimer to ensure the events will be raised on the same thread as the UI.
  2. Recreate the bindable collection every cycle to ensure the PropertyChanged event is triggered, as the members are constant and only the properties were being modified.
  3. Ensure to unsubscribe the dispatcher timer event when swicth view, and subscribe it again when returning.

It was a combination of all the 3 points above.