Search code examples
c#wpfrefreshcaliburn.microitemscontrol

ItemsControl - Bindable Collection - Ui update by Timer Event


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?


Solution

  • 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.