【问题标题】:WPF loading animation on a separate UI thread? (C#)WPF 在单独的 UI 线程上加载动画? (C#)
【发布时间】:2011-04-17 22:02:16
【问题描述】:

好的,我有一个加载动画,它在填充大型 DataTable 时运行,让用户知道程序没有冻结。我的动画工作正常,但是在 DataTable 更新时它也冻结了。有没有办法让多个 UI 线程,让动画在 DataTable 加载信息时继续运行?

编辑:当前代码如下。

private void CreateFileTable()
{
    file_data = new DataSet();
    data_table = new DataTable();
    file_data.Tables.Add(data_table);

    DataColumn tempCol = new DataColumn("File Name", typeof(string));
    data_table.Columns.Add(tempCol);

    tempCol = new DataColumn("Ext", typeof(string));
    data_table.Columns.Add(tempCol);

    tempCol = new DataColumn("Size", typeof(string));
    data_table.Columns.Add(tempCol);

    tempCol = new DataColumn("Created", typeof(Label));
    data_table.Columns.Add(tempCol);

    tempCol = new DataColumn("Modified", typeof(Label));
    data_table.Columns.Add(tempCol);

    tempCol = new DataColumn("Accessed", typeof(Label));
    data_table.Columns.Add(tempCol);

    tempCol = new DataColumn("Location", typeof(string));
    data_table.Columns.Add(tempCol);

    File_List.ItemsSource = file_data.Tables[0].DefaultView;
}

private void PopulateDirectories(string[] directories)
{
    for (int i = 0; i < directories.Length; i++)
    {
        DirectoryInfo tempDirInfo = new DirectoryInfo(directories[i]);

        bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System);

        if (!isSystem)
        {
            DataRow tempRow = data_table.NewRow();
            tempRow["File Name"] = tempDirInfo.Name;
            tempRow["Ext"] = "";
            tempRow["Size"] = "";

            tempLabel = new Label();
            tempLabel.Padding = new Thickness(2, 0, 2, 0);
            tempLabel.Content = tempDirInfo.CreationTime.ToLongDateString() + ", " + tempDirInfo.CreationTime.ToLongTimeString();

            tempRow["Created"] = tempLabel;

            tempLabel = new Label();
            tempLabel.Padding = new Thickness(2, 0, 2, 0);
            tempLabel.Content = tempDirInfo.LastWriteTime.ToLongDateString() + ", " + tempDirInfo.LastWriteTime.ToLongTimeString();

            tempRow["Modified"] = tempLabel;

            tempLabel = new Label();
            tempLabel.Padding = new Thickness(2, 0, 2, 0);
            tempLabel.Content = tempDirInfo.LastAccessTime.ToLongDateString() + ", " + tempDirInfo.LastAccessTime.ToLongTimeString();

            tempRow["Accessed"] = tempLabel;
            tempRow["Location"] = tempDirInfo.FullName;

            data_table.Rows.Add(tempRow);
        }
    }
}

private void PopulateFiles(string[] files)
{
    for (int i = 0; i < files.Length; i++)
    {
        FileInfo tempFileInfo = new FileInfo(files[i]);

        bool isSystem = ((File.GetAttributes(files[i]) & FileAttributes.System) == FileAttributes.System);

        if (!isSystem)
        {
            DataRow tempRow = data_table.NewRow();
            tempRow["File Name"] = tempFileInfo.Name;
            tempRow["Ext"] = tempFileInfo.Extension;

            int fileSize = (int)tempFileInfo.Length;

            if (fileSize > 1048576)
            {
                tempRow["Size"] = "" + fileSize / 1048576 + " MB";
            }
            else if (fileSize > 1024)
            {
                tempRow["Size"] = "" + fileSize / 1024 + " KB";
            }
            else
            {
                tempRow["Size"] = "" + fileSize + " B";
            }

            tempLabel = new Label();
            tempLabel.Padding = new Thickness(2, 0, 2, 0);
            tempLabel.Content = tempFileInfo.CreationTime.ToLongDateString() + ", " + tempFileInfo.CreationTime.ToLongTimeString();

            tempRow["Created"] = tempLabel;

            tempLabel = new Label();
            tempLabel.Padding = new Thickness(2, 0, 2, 0);
            tempLabel.Content = tempFileInfo.LastWriteTime.ToLongDateString() + ", " + tempFileInfo.LastWriteTime.ToLongTimeString();

            tempRow["Modified"] = tempLabel;

            tempLabel = new Label();
            tempLabel.Padding = new Thickness(2, 0, 2, 0);
            tempLabel.Content = tempFileInfo.LastAccessTime.ToLongDateString() + ", " + tempFileInfo.LastAccessTime.ToLongTimeString();

            tempRow["Accessed"] = tempLabel;
            tempRow["Location"] = tempFileInfo.DirectoryName;

            data_table.Rows.Add(tempRow);
        }
    }
}

private string GetSelectedPath(TreeViewItem selectedNode)
{
    return selectedNode.Tag as string;
}

private void PopulateFileList()
{
    PopulateDirectories(Directory.GetDirectories(GetSelectedPath((TreeViewItem)Dir_Tree.SelectedItem)));
    PopulateFiles(Directory.GetFiles(GetSelectedPath((TreeViewItem)Dir_Tree.SelectedItem)));
}

private void UpdateFileList()
{
    LoadingWheel.Visibility = System.Windows.Visibility.Visible;

    CreateFileTable();
    PopulateFileList();
    TxtFoundCount.Text = "Files/Folders Found: " + File_List.Items.Count;

    LoadingWheel.Visibility = System.Windows.Visibility.Hidden;
}

我尝试过使用 BackgroundWorker 并在其中调用 UpdateFileList() 方法,但我没有任何运气。

编辑:下面是我的 BackgroundWorker 代码。

private BackgroundWorker bgWorker1;

private void InitializeBackgroundWorker()
{
    bgWorker1 = new BackgroundWorker();
    bgWorker1.DoWork += new DoWorkEventHandler(bgWorker1_DoWork);
    bgWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker1_RunWorkerCompleted);
}

private void bgWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    if (Dispatcher.CheckAccess())
    {
        PopulateFileList();
    }
    else
    {
        Dispatcher.Invoke(new Action(() => PopulateFileList()));
    }
}

private void bgWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    TxtFoundCount.Text = "Files/Folders Found: " + File_List.Items.Count;
    LoadingWheel.Visibility = System.Windows.Visibility.Hidden;
}

private void UpdateFileList()
{
    LoadingWheel.Visibility = System.Windows.Visibility.Visible;
    CreateFileTable();
    bgWorker1.RunWorkerAsync();
}

我在调用 InitializeBackgroundWorker() 和 UpdateFileList() 的 TreeView 上有一个 SelectionChanged 事件。列表仍然加载,我没有收到任何错误,但加载动画永远不可见。

任何帮助将不胜感激。

谢谢。

【问题讨论】:

  • 你的后台工作人员的东西在哪里?
  • 我把 BackgroundWorker 的代码拿回来了,因为它有更多的代码并且没有任何帮助。我可以把它放回去,但我很确定我做错了。
  • 在这种情况下,您可能会在“打开”LoadingWheel 之后从 UpdateFileList 调用后台工作人员的 RunWorkerAsync 函数。将两个函数调用移至 bgWorker_DoWork,将 TextBlock 的更新移至 bgWorker_ProgressChanged,并将 LoadingWheel 关闭至 bgWorker_RunWorkerCompleted。查看更新的答案。
  • 我已经为上面的 BackgroundWorker 发布了我的代码。我会试试你说的,看看有没有帮助!再次感谢您抽出时间与我合作!
  • 不——那是错误的。请参阅下面的更新答案。通过在 DoWork 方法中调用 Invoke 内容,您只是将工作移回 UI 线程。

标签: c# wpf multithreading animation loading


【解决方案1】:

只有一个 UI 线程。您需要做的是在不同的线程上加载 DataTable 中的数据。

如果您想显示 DataTable 加载过程中的进度(直接或通过 ProgressBar 或其他机制),BackgroundWorker 是一种相当直接的方式。

更新:非常简单的后台工作人员示例
这是一个相当简单的例子。它将 100 个随机数添加到集合中,在每个之间暂停线程一小段时间以模拟长时间的加载过程。您可以简单地将其剪切并粘贴到您自己的测试项目中以查看它的工作原理。

需要注意的是,繁重的工作(需要一段时间的工作)是在 DoWork 中完成的,而所有 UI 更新都是在 ProgressChanged 和 RunWorkerCompleted 中完成的。实际上,在 DoWork 处理程序中创建了一个单独的列表(数字),因为全局 mNumbers 集合在 UI 线程上,无法在 DoWork 处理程序中交互。

XAML

<Button x:Name="btnGenerateNumbers"
        Grid.Row="1"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Content="Generate Numbers" />

C# 代码隐藏

BackgroundWorker bgWorker = new BackgroundWorker();
ObservableCollection<int> mNumbers = new ObservableCollection<int>();

public Window1()
{
    InitializeComponent();
    bgWorker.DoWork += 
        new DoWorkEventHandler(bgWorker_DoWork);
    bgWorker.ProgressChanged += 
        new ProgressChangedEventHandler(bgWorker_ProgressChanged);
    bgWorker.RunWorkerCompleted += 
        new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
    bgWorker.WorkerReportsProgress = true;

    btnGenerateNumbers.Click += (s, e) => UpdateNumbers();

    this.DataContext = this;
}

void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    progress.Visibility = Visibility.Collapsed;
    lstItems.Opacity = 1d;
    btnGenerateNumbers.IsEnabled = true;
}

void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    List<int> numbers = (List<int>)e.UserState;
    foreach (int number in numbers)
    {
         mNumbers.Add(number);
    }

    progress.Value = e.ProgressPercentage;
}

void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
    Random rnd = new Random();
    List<int> numbers = new List<int>(10);

    for (int i = 1; i <= 100; i++)
    {
        // Add a random number
        numbers.Add(rnd.Next());            

        // Sleep from 1/8 of a second to 1 second
        Thread.Sleep(rnd.Next(125, 1000));

        // Every 10 iterations, report progress
        if ((i % 10) == 0)
        {
            bgWorker.ReportProgress(i, numbers.ToList<int>());
            numbers.Clear();
        }
    }
}

public ObservableCollection<int> NumberItems
{
    get { return mNumbers; }
}

private void UpdateNumbers()
{
    btnGenerateNumbers.IsEnabled = false;
    mNumbers.Clear();
    progress.Value = 0;
    progress.Visibility = Visibility.Visible;
    lstItems.Opacity = 0.5;

    bgWorker.RunWorkerAsync();
}

【讨论】:

  • 我在上面发布了我当前的代码。我尝试用它实现 BackgroundWorker,但没有运气。它仍然像以前一样冻结 UI。
【解决方案2】:

我写了一个小测试程序来展示 Dispatcher 类的使用。它只需要一个 WPF 窗口和一个名为“listBox”的 ListBox。应该很容易将此解决方案应用于您的问题。

    public void Populate() {
        // for comparison, freezing the ui thread
        for (int i = 0; i < 1000000; i++) {
            listBox.Items.Add(i);
        }
    }

    private delegate void AddItemDelegate(int item);
    public void PopulateAsync() {
        // create a new thread which is iterating the elements
        new System.Threading.Thread(new System.Threading.ThreadStart(delegate() {
            // inside the new thread: iterate the elements
            for (int i = 0; i < 1000000; i++) {
                // use the dispatcher to "queue" the insertion of elements into the UI-Thread
                // DispatcherPriority.Background ensures Animations have a higher Priority and the UI does not freeze
                // possible enhancement: group the "jobs" to small units to enhance the performance
                listBox.Dispatcher.Invoke(new AddItemDelegate(delegate(int item) {
                    listBox.Items.Add(item);
                }), System.Windows.Threading.DispatcherPriority.Background, i);
            }
        })).Start();
    }

【讨论】:

    猜你喜欢
    • 2012-11-06
    • 2014-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多