简体   繁体   English

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

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

I have created a WPF app that has an ObservableCollection>.我创建了一个具有 ObservableCollection> 的 WPF 应用程序。 This is bound to a ListBox which has a DataTemplate to display the information in a tidy manner.这绑定到一个 ListBox,它有一个 DataTemplate 以整洁的方式显示信息。

When I run the app the ListBox populates the rows as expected... but no information is displayed in the DataTemplate.当我运行应用程序时,ListBox 会按预期填充行...但 DataTemplate 中没有显示任何信息。

Here are the Code Parts这是代码部分

WINDOW XAML CODE窗口 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 CODE BEHIND隐藏的窗口代码

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 CODE过程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));
            }
        }
    }
}

Talking Points谈话要点

The ObservableCollection holds Tasks of type ProcessURLResults, and to access these properties of ProcessURLResults you need to use the Results property of the task. ObservableCollection 包含 ProcessURLResults 类型的任务,要访问 ProcessURLResults 的这些属性,您需要使用任务的 Results 属性。

ocDownloadedTasks[0].Result.APIResults;

So how do I make the ListBox's DataTemplate get the Result property?那么如何让 ListBox 的 DataTemplate 获得 Result 属性呢?

I think that your template binding type mismatch.我认为您的模板绑定类型不匹配。

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

is different from不同于

public ObservableCollection<Task<ProcessedURLResult>> AwesomeSauce

and

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

is redundant.是多余的。 you have the assign code in MainWindow()您在 MainWindow() 中有分配代码

ResultList.ItemsSource = ocDownloadedTasks;

try to use wrapper class.尝试使用包装类。 for instance例如

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

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

}

and

public ObservableCollection<ResultWrapper> AwesomeSauce

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

You have to change the DataType to Task (or remove it completely) and then adjust the binding paths:您必须将DataType更改为Task (或将其完全删除),然后调整绑定路径:

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

Note笔记
You definitely should avoid Task.RunSynchronously() as there are scenarios where it will produce a deadlock.您绝对应该避免Task.RunSynchronously()因为在某些情况下它会产生死锁。 Task are designed for asynchronous or concurrent programming. Task是为异步或并发编程而设计的。
To have code execute synchronously and deferred at some random time you should make use of delegates and use one of the Action overloads ( Action<ProcessedURLResult> in your scenario).要让代码同步执行并在某个随机时间延迟,您应该使用委托并使用Action重载之一(在您的场景中为Action<ProcessedURLResult> )。

Then don't bind to this delegate collection directly (or Task collection) but to a dedicated result collection of type ProcessedURLResult you can bind the ItemsSource to.然后不要直接绑定到此委托集合(或Task集合),而是绑定到ProcessedURLResult类型的专用结果集合,您可以将ItemsSource绑定到。 This will give you Intellisense support in XAML too, when writing the DataTemplate .在编写DataTemplate时,这也将为您提供 XAML 中的 Intellisense 支持。

Whether you use Action or stick to Task you will experience UI freeze.无论您使用Action还是坚持使用Task您都会遇到 UI 冻结。 Depending on the individual execution time and count of items, ie total execution time, this freeze will be more or less noticable but is always not desireable and should/ can be avoided.根据个别执行时间和项目数,即总执行时间,这种冻结或多或少会引人注目,但总是不希望的,应该/可以避免。 Therefore if you have access to WebRequestCustomer consider to make it run asynchronously (eg, using TaskCompletionSource in case of posting HTTP requests).因此,如果您有权访问WebRequestCustomer考虑使其异步运行(例如,在发布 HTTP 请求的情况下使用TaskCompletionSource )。

Also your code is mixing both ways to populate the ItemsControl.ItemsSource : you are using Binding and direct assignment.此外,您的代码混合了两种方式来填充ItemsControl.ItemsSource :您正在使用Binding和直接分配。 The latter will override the former and behaves differently.后者将覆盖前者并表现不同。 I recommend Binding set from XAML.我推荐 XAML 中的Binding集。

Tasks should be awaited.应该等待任务。

Do not use Task<ProcessedURLResult> as item type.不要使用Task<ProcessedURLResult>作为项目类型。 Instead, declare a (read-only) collection property like相反,声明一个(只读)集合属性,如

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

and populate it in an async method by awaiting the Task returned from the ProcessPostURL method:并通过等待从 ProcessPostURL 方法返回的 Task 以async方法填充它:

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

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

There is also no need to assign the ListBox's ItemsSource in code behind if you bind it in XAML:如果您在 XAML 中绑定 ListBox 的 ItemsSource,则也无需在后面的代码中分配它:

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

You may also want to replace System.Timers.Timer by DispatcherTimer to avoid the need for calling Dispatcher.Invoke .您可能还想用DispatcherTimer替换System.Timers.Timer以避免需要调用Dispatcher.Invoke

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM