我创建了一个具有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属性?
答案 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
。