0

So, I have a WPF app which starts up with the MainWindow.xaml and then starts a background thread which cycles through pages for the MainWindow. What the thread really does is sets the content to a new page every 20/30 seconds (if there's a better way to do this as well then I'd gladly appreciate a suggestion). So I've built the app using the MVVM pattern so every page has it's own viewmodel class with corresponding data. Each class as some lists containing data to be displayed in a Datagrid table, and the table is bound to the list. But I want to update the list without freezing the UI, essentially asynchronously or on another thread. How do I go about doing this?

I haven't really come up with any solutions as of yet, I thought about locking the viewmodel while updating it but I feel like that would break the UI in some way.

Viewmodel example

private List<ChangeInfo> nonApprovedChanges;

public ÄrendenStatsViewModel()
{
    nonApprovedChanges = new List<ChangeInfo>;
}

public List<ChangeInfo> NonApprovedChanges //this is bound to a datagrid
{
    get { return nonApprovedChanges; }
    set
    {
        if (nonApprovedChanges != value)
        {
            nonApprovedChanges = value;
            OnPropertyChange();
        }
    }
}

Thread that changes slides

private void ManageSlides()
{
    new Thread(() =>
    {
        var page1 = new ExamplePage();
        var page2 = new ExamplePage2();

        while (true)
        {
            Dispatcher.Invoke(new Action(() =>
            {
                Content = page1;
            }));
            Thread.Sleep(1000 * 20);
            Dispatcher.Invoke(new Action(() =>
            {
                Content = page2;
            }
            Thread.Sleep(1000 * 30);
        }
    }).Start();
}

XAML

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Width="*" />
        <RowDefinition Width="15*" />
    </Grid.RowDefinitions>

    <Grid Grid.Row="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="3*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBlock Style="{StaticResource DataGridTitleStyle}" Grid.Column="0" HorizontalAlignment="Left" Text="Not approved" />
        <TextBlock Style="{StaticResource DataGridTitleStyle}" Grid.Column="1" HorizontalAlignment="Right" Text="{Binding NonApprovedChangesCount}" />
    </Grid>

    <DataGrid x:Name="nonApprovedChangesDataGrid" ItemsSource="{Binding NonApprovedChanges}" Style="{StaticResource DataGridStyle}" />
        <DataGrid.Columns>
            <DataGridTextColumn Width="3*" HeaderStyle="{StaticResource DataGridHeaderStyle}" Header="Change" Binding="{Binding Changenumber}" />
            <DataGridTextColumn Width="4*" HeaderStyle="{StaticResource DataGridHeaderStyle}" Header="Requester" Binding="{Binding Requester}" />
            <DataGridTextColumn Width="6*" HeaderStyle="{StaticResource DataGridHeaderStyle}" Header="Date" Binding="{Binding Date}" />
        </DataGrid.Columns>
    </DataGrid>
</Grid>

I'd like to have a separate thread (similar to the page-slider) that continuously updates the list nonApprovedChanges in the example viewmodel. Although it doesn't have to be a separate thread, I just want a niche way to update the List without freezing the UI.

rasqall
  • 33
  • 7
  • 2
    Have you considered making it an observable collection or just making the collection property itself raise a change notification event? That way the UI will automatically respond to data source changes. https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification – Charleh Nov 11 '19 at 09:56
  • 1
    Also I wouldn't be using `Thread.Sleep` here - https://stackoverflow.com/questions/8815895/why-is-thread-sleep-so-harmful – Charleh Nov 11 '19 at 09:58
  • Use a timer, preferrably the WPF DispatcherTimer class. – Clemens Nov 11 '19 at 10:05
  • @Charleh Haven't looked into ObservableCollections but might as well do. Although at the bottom of the question on the link of your second comment, he states that you can use it when you're supposed to use it when you want to wait for a certain amount of seconds? – rasqall Nov 11 '19 at 10:06
  • Yes true, you can get away with using `Thread.Sleep` here but as @Clemens said, use `DispatcherTimer` - it integrates with the UI thread in the first place so it just abstracts away that `Dispatcher.Invoke` call. – Charleh Nov 11 '19 at 10:25
  • You could then also make your DispatcherTimer.Tick event handler `async` to execute and await background task(s). – Clemens Nov 11 '19 at 10:29
  • What is Contents and where it is bound to? And does it Freezes a significant time? Dispatcher.Invoke() always freezes the UI but normally its an insignificant time when the code is efficient. What is the time consuming operation you do in changing the value of Content? Is that ManageSlides() not inside the viewModel? – Himesh Sameera Nov 11 '19 at 11:13
  • @HimeshSameera The contents are lists, similar to the one in the example. There are five of them exactly the same that contains different data. But the types are the same and the properties are the same, hence I only included one. The ManageSlides() is a part of MainWindow.xaml.cs, so the MainWindow's code-behind. The freeze wouldn't be that long, only a couple of seconds, if even a whole second. You mean that I could lock a list for that time and then release it to the UI again without crashing the program? – rasqall Nov 11 '19 at 11:24
  • So, essentially, the expensive operation is the process of changing the `Content` of the `MainWindow`? Do these bound lists contains 1000s of elements? If so, background threads won't help, but `virtualization` might. – TripleAccretion Nov 11 '19 at 11:39
  • @ArtemK No the lists only contain 10ths of items each, very small lists. Yes I would think that changing the content is the expensive operation here. – rasqall Nov 11 '19 at 11:53
  • @rasqall If you can share the xaml also I can find what is the expensive operation. I cant think what is your reason to change the content variable instead of the list that is binded or the datacontext. – Himesh Sameera Nov 11 '19 at 11:56
  • Hmm, yes, XAML would be alot of help. @HimeshSameera is right in that you would ordinarily simply change the list in your VM, not update your UI from code-behind with another thread. – TripleAccretion Nov 11 '19 at 12:01
  • @ArtemK What I ended up doing was changing the list through the property that is also bound to the grid, it worked and the datagrid updated accordingly. Is this a good idea? That way I can update with a new list that is generated from another thread which does API calls. – rasqall Nov 11 '19 at 14:03
  • @rasqall Yes! It is exactly what MVVM is supposed to be. You manipulate data - views change through bindings. – TripleAccretion Nov 11 '19 at 14:08
  • 1
    @ArtemK Feels good to hear, also to finally understand it. Thanks! – rasqall Nov 11 '19 at 14:17

0 Answers0