我的 DataTemplate 未在 WPF 应用程序中显示来自 ObservableCollection<Task<myresultclass>> 的数据

My DataTemplate is not displaying data in WPF app from an ObservableCollection<Task<myresultclass>>

我创建了一个具有 ObservableCollection> 的 WPF 应用程序。这绑定到一个 ListBox,它有一个 DataTemplate 以整齐的方式显示信息。

当我 运行 应用程序时,ListBox 按预期填充行...但 DataTemplate 中未显示任何信息。

这是代码部分

WINDOW XAML 代码

<Window
    x:Class="web.app.smash.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:local="clr-namespace:web.app.smash"
    xmlns:m="clr-namespace:web.app.smash.lib.Helpers;assembly=web.app.smash.lib"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    x:Name="MainPage"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Window.Resources>


        <DataTemplate x:Key="ResultListItemTemplate" DataType="{x:Type m:ProcessedURLResult}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="12" />
                    <RowDefinition Height="12" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>

                <StackPanel Orientation="Horizontal">
                    <TextBlock
                        FontSize="8"
                        FontWeight="Bold"
                        Text="Start time:" />
                    <TextBlock
                        x:Name="StartTime"
                        Width="50"
                        Margin="4,0,0,0"
                        FontSize="8"
                        Text="{Binding StartTime, Mode=OneWay}" />
                </StackPanel>

                <StackPanel Grid.Row="1" Orientation="Horizontal">
                    <TextBlock
                        FontSize="8"
                        FontWeight="Bold"
                        Text="End time:" />
                    <TextBlock
                        x:Name="EndTime"
                        Margin="4,0,0,0"
                        FontSize="8"
                        Text="{Binding EndTime, Mode=OneWay}" />
                </StackPanel>

                <StackPanel Grid.Column="1" Orientation="Horizontal">
                    <TextBlock
                        FontSize="8"
                        FontWeight="Bold"
                        Text="Milli seconds:" />
                    <TextBlock
                        x:Name="MilliSecondsTaken"
                        Margin="4,0,0,0"
                        FontSize="8"
                        Text="{Binding MillisecondsTaken, Mode=OneWay}" />
                </StackPanel>

                <StackPanel
                    Grid.Row="1"
                    Grid.Column="1"
                    Orientation="Horizontal">
                    <TextBlock
                        FontSize="8"
                        FontWeight="Bold"
                        Text="HTTP ststus code:" />
                    <TextBlock
                        x:Name="HTTPStatusCode"
                        Margin="4,0,0,0"
                        FontSize="8"
                        Text="{Binding HTTPStatusCode, Mode=OneWay}" />
                </StackPanel>

                <StackPanel
                    Grid.Row="2"
                    Grid.ColumnSpan="2"
                    Orientation="Horizontal">
                    <TextBlock
                        FontSize="8"
                        FontWeight="Bold"
                        Text="Error message:" />
                    <TextBlock
                        x:Name="ErrorMessage"
                        Height="22"
                        Margin="4,0,0,0"
                        FontSize="8"
                        Text="{Binding ErrorMessage, Mode=OneWay}"
                        TextWrapping="Wrap" />
                </StackPanel>

                <StackPanel
                    Grid.Row="3"
                    Grid.ColumnSpan="2"
                    Orientation="Horizontal">
                    <TextBlock
                        FontSize="8"
                        FontWeight="Bold"
                        Text="API results" />
                    <TextBlock
                        x:Name="APIResults"
                        Height="42"
                        Margin="4,0,0,0"
                        FontSize="8"
                        Text="{Binding APIResults, Mode=OneWay}"
                        TextWrapping="Wrap" />
                </StackPanel>




            </Grid>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="32" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <StackPanel
            Grid.Row="0"
            Grid.Column="0"
            Orientation="Vertical">
            <Button
                x:Name="SubmitCustomerGet"
                Margin="0,0,0,12"
                Click="SubmitCustomerGet_Click"
                Content="Get a Customer" />
            <Button
                x:Name="StartPerformanceTests"
                Margin="0,0,0,4"
                Click="StartPerformanceTests_Click"
                Content="Start Tests" />
            <Button
                x:Name="StopPerformanceTests"
                Margin="0,0,0,4"
                Click="StopPerformanceTests_Click"
                Content="Stop Tests" />
        </StackPanel>

        <StackPanel
            Grid.Row="0"
            Grid.Column="1"
            Orientation="Vertical">
            <TextBlock x:Name="CountURLAdded" Background="#FFFBFFA7" />
            <TextBlock x:Name="CountURLWaiting" Background="#FFEA9393" />
            <TextBlock x:Name="CountURLFinished" Background="#FFB7EEB1" />
        </StackPanel>

        <TextBlock
            x:Name="InformationMessage"
            Grid.Row="1"
            Grid.ColumnSpan="2"
            Background="#FF646464" />

        <ListBox
            x:Name="ResultList"
            Grid.Row="2"
            Grid.ColumnSpan="3"
            ItemTemplate="{DynamicResource ResultListItemTemplate}"
            ItemsSource="{Binding ElementName=MainPage, Path=AwesomeSauce, Mode=OneWay}" />
    </Grid>
</Window>

WINDOW 代码隐藏

using System;
using System.Timers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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;
using web.app.smash.lib;
using web.app.smash.lib.Helpers;
using System.Net.Http;
using System.Threading;
using Timer = System.Timers.Timer;
using System.Diagnostics;
using System.Collections.ObjectModel;

namespace web.app.smash
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        //This is the URL to the webAPI
        private const string BASEURL = "http://localhost:23653/";


        public ObservableCollection<Task<ProcessedURLResult>> AwesomeSauce
        {
            get
            { return ocDownloadedTasks; }
            set
            { ocDownloadedTasks = value; }
        }

        private ObservableCollection<Task<ProcessedURLResult>> ocDownloadedTasks;

        private WebRequestCustomer wc = new WebRequestCustomer();
        private CancellationTokenSource cts;
        private CancellationToken ct;

        private HttpClient client = new HttpClient();

        private long counturladded = 0;
        private long counturlwaiting = 0;
        private long counturlfinihed = 0;

        private Timer smashtimer;


        public MainWindow()
        {
            Debug.WriteLine("App started");

            InitializeComponent();


            SetupTimer(1000);

            ocDownloadedTasks = new ObservableCollection<Task<ProcessedURLResult>>();

            ocDownloadedTasks.CollectionChanged += OcDownloadedTasks_CollectionChanged;

            ResultList.ItemsSource = ocDownloadedTasks;
        }

        private void OcDownloadedTasks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            foreach(Task t in e.NewItems)
            {
                if(t.Status == TaskStatus.Created)
                {
                    t.RunSynchronously();
                }
            }
        }

        private void SetupTimer(double interval)
        {
            Debug.WriteLine("Timer set to:" + interval.ToString());

            smashtimer = new System.Timers.Timer(interval);

            Debug.WriteLine("Timer event handler set");
            smashtimer.Elapsed += Smashtimer_Elapsed;

            smashtimer.AutoReset = true;
        }

        private void Smashtimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            //Debug.WriteLine("Timer event handler elapsed start: " + e.SignalTime.ToString());
            //this.urlList.Add(BASEURL);


            //Debug.WriteLine("ProcessAll: Returns a collection of tasks (In Loop)");
            //// ***Create a query that, when executed, returns a collection of tasks.
            //TasksList = from url in this.urlList select wc.ProcessPostURL(url, client, ct);

            //Debug.WriteLine("ProcessAll: Start processing the list of Tasks (In Loop)");
            //downloadTasks.AddRange(TasksList.ToList());


            //downloadTasks.Add(wc.ProcessPostURL(BASEURL, client, ct));

            App.Current.Dispatcher.Invoke((Action)delegate
            {
                ocDownloadedTasks.Add(wc.ProcessPostURL(BASEURL, client, ct));
            });



            //TasksList = null;

            counturladded += 1;
        }

        private async void SubmitCustomerGet_Click(object sender, RoutedEventArgs e)
        {
            await wc.GetCustomerByID(BASEURL, 6);
            //ResponsesList.Inlines.Add(wc.DisplayResults);
            //ResponsesList.Inlines.Add(new LineBreak());
            InformationMessage.Text = "Get single customer";
        }

        private void StartPerformanceTests_Click(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine("Start Performance button: Clicked");


            Debug.WriteLine("Start Performance button: Create the cancelation token");
            //Create the cancellation token
            cts = new CancellationTokenSource();
            ct = cts.Token;

            Debug.WriteLine("Start Performance button: Timer started");
            smashtimer.Start();


            InformationMessage.Text = "Timer Started";
        }

        private void StopPerformanceTests_Click(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine("Stop performance button: Clicked");

            Debug.WriteLine("Stop performance button: Timer stopped");
            smashtimer.Stop();
            InformationMessage.Text = "Timer Stopped";
        }

        //private void DisplayResults(ProcessedURLResult pur)
        //{
        //    StringBuilder sb = new StringBuilder();

        //    if (pur.ErrorMessage==null)
        //    {
        //        sb.Append("Milliseconds: " + pur.MillisecondsTaken.ToString());
        //        sb.Append("API Result: " + pur.APIResults);


        //        ResponsesList.Inlines.Add(sb.ToString());
        //        ResponsesList.Inlines.Add(new LineBreak());

        //    }
        //    else
        //    {
        //        sb.Append("Error: " + pur.ErrorMessage);

        //        ResponsesList.Inlines.Add(sb.ToString());
        //        ResponsesList.Inlines.Add(new LineBreak());
        //    }
        //    ResponsesList.InvalidateVisual();

        //}


        //private void DisplayInformation()
        //{
        //    CountURLAdded.Text = counturladded.ToString();
        //    CountURLWaiting.Text = counturlwaiting.ToString();
        //    CountURLFinished.Text = counturlfinihed.ToString();
        //}
    }
}

处理 URL 结果代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace web.app.smash.lib.Helpers
{
    public class ProcessedURLResult : INotifyPropertyChanged
    {
        private string _apiresults;
        private long _millisecondsTaken;
        private DateTime _startTime;
        private DateTime _endTime;
        private string _hTTPStatusCode;
        private string _errorMessage;

        public string APIResults
        {
            get
            {
                return _apiresults;
            }
            set
            {
                _apiresults = value;
                OnPropertyChanged(nameof(APIResults));
            }
        }

        public long MillisecondsTaken
        {
            get
            {
                return _millisecondsTaken;
            }
            set
            {
                _millisecondsTaken = value;
                OnPropertyChanged(nameof(MillisecondsTaken));
            }
        }

        public DateTime StartTime
        {
            get
            {
                return _startTime;
            }
            set
            {
                _startTime = value;
                OnPropertyChanged(nameof(StartTime));
            }
        }

        public DateTime EndTime
        {
            get
            {
                return _endTime;
            }
            set
            {
                _endTime = value;
                OnPropertyChanged(nameof(EndTime));
            }
        }

        public string HTTPStatusCode
        {
            get
            {
                return _hTTPStatusCode;
            }
            set
            {
                _hTTPStatusCode = value;
                OnPropertyChanged(nameof(HTTPStatusCode));
            }
        }

        public string ErrorMessage 
        {
            get
            {
                return _errorMessage;
            }
            set
            {
                _errorMessage = value;
                OnPropertyChanged(nameof(ErrorMessage));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        // Create the OnPropertyChanged method to raise the event
        private protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

谈话要点

ObservableCollection 包含 ProcessURLResults 类型的任务,要访问 ProcessURLResults 的这些属性,您需要使用任务的结果 属性。

ocDownloadedTasks[0].Result.APIResults;

那么如何使 ListBox 的 DataTemplate 获得结果 属性?

我认为您的模板绑定类型不匹配。

<DataTemplate x:Key="ResultListItemTemplate" DataType="{x:Type m:ProcessedURLResult}">

不同于

public ObservableCollection<Task<ProcessedURLResult>> AwesomeSauce

ItemsSource="{Binding ElementName=MainPage, Path=AwesomeSauce, Mode=OneWay}" />

是多余的。您在 MainWindow()

中有分配代码
ResultList.ItemsSource = ocDownloadedTasks;

尝试使用包装器 class。例如

public class ResultWrapper
{
    public Task<ProcessedURLResult> InnerTask { get; set; }

    public ProcessedURLResult Result
    {
        get
        {
            return InnerTask.Result;
        }
    }

}

public ObservableCollection<ResultWrapper> AwesomeSauce

XAML 是

<DataTemplate x:Key="ResultListItemTemplate" DataType="{x:Type ResultWrapper}">
...
    <TextBlock
                    x:Name="StartTime"
                    Width="50"
                    Margin="4,0,0,0"
                    FontSize="8"
                    Text="{Binding Result.StartTime, Mode=OneWay}" />
....

您必须将 DataType 更改为 Task(或完全删除它),然后调整绑定路径:

<DataTemplate DataType="{x:Type Task}">
  <TextBlock Text="{Binding Result.ErrorMessage}"/>
</DataTemplate>

备注
你绝对应该避免 Task.RunSynchronously() 因为在某些情况下它会产生死锁。 Task 专为异步或并发编程而设计。
要让代码同步执行并在某个随机时间延迟,您应该使用委托并使用 Action 重载之一(在您的场景中为 Action<ProcessedURLResult> )。

然后不要直接绑定到此委托集合(或 Task 集合),而是绑定到 ProcessedURLResult 类型的专用结果集合,您可以将 ItemsSource 绑定到。在编写 DataTemplate.

时,这也会在 XAML 中为您提供 Intellisense 支持

无论您使用 Action 还是坚持使用 Task,您都会遇到 UI 冻结。根据个别执行时间和项目数量,即总执行时间,这种冻结或多或少会引起注意,但总是不可取的,应该/可以避免。因此,如果您有权访问 WebRequestCustomer,请考虑使其异步 运行(例如,在发布 HTTP 请求时使用 TaskCompletionSource)。

此外,您的代码混合了两种方式来填充 ItemsControl.ItemsSource:您正在使用 Binding 和直接赋值。后者将覆盖前者并且表现不同。我建议从 XAML.

设置 Binding

应该等待任务。

不要使用 Task<ProcessedURLResult> 作为项目类型。相反,声明一个(只读)集合 属性 如

public ObservableCollection<ProcessedURLResult> AwesomeSauce { get; } =
    new ObservableCollection<ProcessedURLResult>();

并通过等待从 ProcessPostURL 方法返回的任务在 async 方法中填充它:

private async void Smashtimer_Elapsed(object sender, ElapsedEventArgs e)
{
    ...
    var result = await wc.ProcessPostURL(BASEURL, client, ct)

    Dispatcher.Invoke(() => AwesomeSauce.Add(result));
}

如果在XAML中绑定ListBox的ItemsSource,也不需要在后面的代码中分配它:

<ListBox ItemsSource="{Binding ElementName=MainPage, Path=AwesomeSauce}" .../>

您可能还想用 DispatcherTimer 替换 System.Timers.Timer 以避免调用 Dispatcher.Invoke