FukYouAll
FukYouAll

Reputation: 111

Change WPF Label content while program is loading

I have a WPF application that loads a lot of content and UI updates in the same code blocks, and I want to show the progress or what task is doing in the Label.Content property, currently I'm doing this:

void LoadEverything()
{
    UpdateContentLabel("Loading items");
    foreach(string i in stringArrayItems)
    {
         UpdateContentLabel("Loading " + i + " info...");
         //LoadTasks
    }

    UpdateContentLabel("Loading history");
}

void UpdateContentLabel(string Task)
{
    myLoadLabel.Content = Task;
}

The first problem is that label content is not updated, I'm aware that UI Thread and the task thread are the same and that's why UI freezes, I tried to use BackgroundWorker to put the load tasks on it and this.Dispatcher.BeginInvoke((Action)delegate { /*UI Updates */ }); the UI updates (like create custom ListBoxItem and add it to a ListBox) and throws a TargetInvocationException in app.ShowDialog(); (app is not the first form, is the mainApp dialog and is created in a login window).

BackgroundWorker loadInfo = new BackgroundWorker();

private void app_Loaded(object sender, RoutedEventArgs e)
{
    loadInfo.DoWork += loadInfo_DoWork;
    loadInfo.RunWorkerCompleted += loadInfo_RunWorkerCompleted;
    loadInfo.WorkerReportsProgress = true;
    loadInfo.RunWorkerAsync();
}
public void loadInfo_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    UpdateContentLabel("Load complete :)");
}

public void loadInfo_DoWork(object sender, DoWorkEventArgs e)
{
    this.Dispatcher.BeginInvoke((Action)delegate { UpdateContentLabel("Loading items"); });
    foreach(string i in stringArrayItems)
    {
         this.Dispatcher.BeginInvoke((Action)delegate { UpdateContentLabel("Loading " + i + " info..."); });
         //LoadTasks
         this.Dispatcher.BeginInvoke((Action)delegate { /*UI Updates */ });
    }
}

So, the question is what can I do to show the current task in the label aforementioned?

Edit: Project versión of .NET is 3.5.

Upvotes: 1

Views: 1596

Answers (2)

user3537866
user3537866

Reputation:

Here, I have created sample code to match your code scenario. Here, idea is not accessing the UI element directly from code behind (in your case that is ListBox). By using INotifyPropertyChanged you can utilize the List collection to bind to your ListBox's ItemsSource and update the bound collection on code behind that eliminate your UI thread attachment requirement. So here you go:

<Grid x:Name="MainGrid">
    <ListBox ScrollViewer.VerticalScrollBarVisibility="Visible" x:Name="MainListBox" ItemsSource="{Binding AvailableListItems}">
    </ListBox>
</Grid>

Code behind:

public partial class MainWindow : INotifyPropertyChanged
{
    #region INotifyPropertyChanged section
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
    #endregion

    private List<int> availableListItems;
    public List<int> AvailableListItems
    {
        get
        {
            return availableListItems;
        }
        set
        {
            availableListItems = value;
            OnPropertyChanged("AvailableListItems");
        }
    }

    public MainWindow()
    {
        InitializeComponent();
        AvailableListItems = new List<int>();
        this.DataContext = this;
    }

    BackgroundWorker loadInfo = new BackgroundWorker();

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        loadInfo.DoWork += loadInfo_DoWork;
        loadInfo.WorkerReportsProgress = true;
        loadInfo.RunWorkerAsync();
    }

    public void loadInfo_DoWork(object sender, DoWorkEventArgs e)
    {
        for(int i = 0; i < 2000; i++)
        {
            AvailableListItems.Add(i);
            Thread.Sleep(1);
            OnPropertyChanged("OnPropertyChanged");
        }
    }
}

Hope, this will be helpful.

Upvotes: 0

Noctis
Noctis

Reputation: 11763

Put your logic inside the foreach loop, not inside a second BeginInvoke.

Here's a running functional example:

public partial class MainWindow : Window
{
    BackgroundWorker loadInfo = new BackgroundWorker();

    private List<string> stringArrayItems = new List<string>{
        "First file",
        "second file",
        "third file ",
        "fourth file "
    };

    public MainWindow()
    {
        InitializeComponent();

        loadInfo.DoWork += loadInfo_DoWork;
        loadInfo.RunWorkerCompleted += loadInfo_RunWorkerCompleted;
        loadInfo.WorkerReportsProgress = true;
        loadInfo.RunWorkerAsync();
    }

    public void loadInfo_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        UpdateContentLabel("Load complete :)");
    }

    public void loadInfo_DoWork(object sender, DoWorkEventArgs e)
    {
        this.Dispatcher.BeginInvoke((Action)(() => UpdateContentLabel("Loading items")));
        foreach (string i in stringArrayItems)
        {
            this.Dispatcher.BeginInvoke((Action)(() =>
                UpdateContentLabel("Loading " + i + " info...")
            ));

            //LoadTasks
            Thread.Sleep(1000);
            Console.Out.WriteLine("Loading {0} and sleeping for a second.", i);
        }
    }

    void UpdateContentLabel(string Task)
    {
        MyLabel.Content = Task;
    }
}

Upvotes: 1

Related Questions