Xceed WPF 工具包 - BusyIndi​​cator - 在 C# 中创建动态加载消息,在数据模板中绑定数据

Xceed WPF Toolkit - BusyIndicator - creating dynamic loading messages in C#, data binding in a data template

我正在开发一个使用 Xceed BusyIndi​​cator 的小型 WPF 应用程序。我在动态更新加载消息时遇到了一些麻烦,因为内容包含在 DataTemplate 中。我熟悉的数据绑定或设置文本值的方法不起作用。

我做了一些研究 - 看起来其他人也遇到过这个问题。似乎是 answered here,但由于答案不在上下文中,我无法完全弄清楚它在我的代码中如何工作。

这是我的示例代码,如果有人能帮助我理解我所缺少的,我将不胜感激。这增加了使用 BackgroundWorker 线程的挑战。我使用它是因为我预计这将是一个漫长的 运行ning 进程 - 最终该操作将启动一个 SQL 作业,该作业将处理可能需要长达 15 分钟的项目。我的计划是让线程定期 运行 一个存储过程来获取剩余项目的数量以处理和更新加载消息。

MainWindow.xaml:

<Window x:Class="WPFTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        xmlns:local="clr-namespace:WPFTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <xctk:BusyIndicator x:Name="AutomationIndicator">
        <xctk:BusyIndicator.BusyContentTemplate>
            <DataTemplate>
                <StackPanel Margin="4">
                    <TextBlock Text="Sending Invoices" FontWeight="Bold" HorizontalAlignment="Center"/>
                        <WrapPanel>
                            <TextBlock Text="Items remaining: "/>
                        <TextBlock x:Name="_ItemsRemaining" Text="{Binding Path=DataContext.ItemsRemaining, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
                        </WrapPanel>
                    

                </StackPanel>
            </DataTemplate>
        </xctk:BusyIndicator.BusyContentTemplate>
        <Grid>
            <StackPanel>
                <TextBlock Text="Let's test this thing" />
                <Button x:Name="_testBtn" Content="Start" Width="100" HorizontalAlignment="Left" Click="testBtn_Click"/>
                <TextBlock Text="{Binding ItemsRemaining}"/>
            </StackPanel>
        </Grid>
    </xctk:BusyIndicator>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        
        public MainWindow()
        {
            InitializeComponent();
            UpdateItemsRemaining(0);
        }


        public class ItemCountDown
        {
            //One Idea was to try and set a data binding variable
            public string ItemsRemaining { get; set; }
        }

        public void UpdateItemsRemaining(int n)
        {
            ItemCountDown s = new ItemCountDown();
            {
                s.ItemsRemaining = n.ToString();
            };
            //this.AutomationIndicator.DataContext = s;      Works during initiation, but not in the thread worker.



    }


        private void testBtn_Click(object sender, RoutedEventArgs e)
        {
            //Someone clicked the button, run the Test Status 
            TestStatus();
        }


        public void TestStatus()
        {
            // Normally I'd start a background worker to run a loop to check that status in SQL
            BackgroundWorker getStatus = new BackgroundWorker();
            getStatus.DoWork += (o, ea) =>
            {

                //Normally there's a sql connection being opened to check a SQL Job, and then I run a loop that opens the connection to check
                //the status until it either fails or successfully ended.  

                //but for this test, I'll just have it run for 15 seconds, counting down fake items.

                int fakeItems = 8;

                do  //
                {

                    //Idea One - write to the text parameter.  But can't find it in the template
                    //Dispatcher.Invoke((Action)(() => _ItemsRemaining.Text = fakeItems));

                    //Idea two - use data binding to update the value.  Data binding works just find outside of the Data Template but is ignored in the template
                    UpdateItemsRemaining(fakeItems);


                    //subtract one from fake items and wait a second.
                    fakeItems--;
                    Thread.Sleep(1000);

                } while (fakeItems > 0);


            };

            getStatus.RunWorkerCompleted += (o, ea) =>
            {
                //work done, end it.
                AutomationIndicator.IsBusy = false;

            };

            AutomationIndicator.IsBusy = true;
            getStatus.RunWorkerAsync();

        }
    }
}

感谢您的审阅,感谢您提供的任何帮助或指导。

DataContext 设置为您的 ItemCountDown 对象并实施 INotifyPropertyChanged:

public class ItemCountDown : INotifyPropertyChanged
{
    private string _itemsRemaining;
    public string ItemsRemaining
    {
        get { return _itemsRemaining; }
        set { _itemsRemaining = value; NotifyPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public partial class MainWindow : Window
{
    private readonly ItemCountDown s = new ItemCountDown();

    public MainWindow()
    {
        InitializeComponent();
        DataContext = s;
        UpdateItemsRemaining(0);
    }

    public void UpdateItemsRemaining(int n)
    {
        s.ItemsRemaining = n.ToString();
    }


    private void testBtn_Click(object sender, RoutedEventArgs e)
    {
        TestStatus();
    }

    public void TestStatus()
    {
        ...
    }
}

然后您可以直接绑定到 XAML 标记的 DataTemplate 中的 属性:

<TextBlock x:Name="_ItemsRemaining" Text="{Binding Path=DataContext.ItemsRemaining, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>