我的 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
。
我创建了一个具有 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
.
无论您使用 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
。