我的DataTemplate没有从ObservableCollection <Task <myresultclass >>在WPF应用程序中显示数据

时间:2019-11-26 02:35:41

标签: c# wpf data-binding task observablecollection

我创建了一个具有ObservableCollection的WPF应用程序。这被绑定到一个ListBox上,该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>

隐藏窗口代码

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();
        //}
    }
}

PROCESSURLRESULTS代码

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的这些属性,您需要使用任务的Results属性。

ocDownloadedTasks[0].Result.APIResults;

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

3 个答案:

答案 0 :(得分:0)

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

<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;

尝试使用包装器类。例如

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}" />
....

答案 1 :(得分:0)

您必须将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

答案 2 :(得分:0)

应该等待任务。

请勿使用Task<ProcessedURLResult>作为项目类型。而是声明一个(只读)集合属性,例如

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

并通过等待async方法中的ProcessPostURL方法返回的Task来填充它:

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

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

如果您在XAML中绑定列表框,则也无需在代码后面分配列表框的ItemsSource:

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

您可能还希望将System.Timers.Timer替换为DispatcherTimer,以避免调用Dispatcher.Invoke