英文:
ObservableCollection binding to dataTemplate with async data
问题
以下是翻译好的部分:
我在WPF中非常新,对我来说很困惑。
主要目标是我想要一个WPF程序,每隔5秒从WebAPI获取数据,并显示数据。
我正在使用这个MS示例代码进行修改:
https://github.com/microsoft/WPF-Samples/blob/main/Data%20Binding/DataTemplatingIntro/MainWindow.xaml
我期望屏幕每秒都会打印新的异步数据,但屏幕上始终为空。
以下是我的源代码:
// mainwindows.xaml
<local:TaskViewModel x:Key="MyTodoList"/>
<local:TaskListDataTemplateSelector x:Key="MyDataTemplateSelector"/>
//
<StackPanel>
<TextBlock FontSize="20" Text="我的任务列表:"/>
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource MyTodoList}}"
ItemTemplateSelector="{StaticResource MyDataTemplateSelector}"
HorizontalContentAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"/>
<TextBlock FontSize="20" Text="信息:"/>
<ContentControl Content="{Binding Source={StaticResource MyTodoList}}"
ContentTemplate="{StaticResource MyTaskTemplate}"/>
</StackPanel>
// TaskViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
...
// 其余代码
我尝试为TaskViewModel实现INotifyPropertyChanged和INotifyCollectionChanged,但仍然无效。
在调试中,程序运行了TaskViewModel中的代码。
感谢Clemens,你帮助我更好地理解了WPF,源代码已上传,供有兴趣的人查看,请随时访问https://github.com/dolphinotaku/demo-wpf/tree/812bd075487dd69a31c691b63fea6eef5931948f
英文:
I am very new in WPF, those are very confuse to me.
the main aim is I want a WPF program that fetches data every 5 seconds from WebAPI, and display the data.
I am using this MS sample code to modify:
https://github.com/microsoft/WPF-Samples/blob/main/Data%20Binding/DataTemplatingIntro/MainWindow.xaml
I except the screen will print new async data on each second, but its always empty on the screen.
Below is my source:
// mainwindows.xaml
<local:TaskViewModel x:Key="MyTodoList"/>
<local:TaskListDataTemplateSelector x:Key="MyDataTemplateSelector"/>
//
<StackPanel>
<TextBlock FontSize="20" Text="My Task List:"/>
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource MyTodoList}}"
ItemTemplateSelector="{StaticResource MyDataTemplateSelector}"
HorizontalContentAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"/>
<TextBlock FontSize="20" Text="Information:"/>
<ContentControl Content="{Binding Source={StaticResource MyTodoList}}"
ContentTemplate="{StaticResource MyTaskTemplate}"/>
</StackPanel>
// TaskViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows.Data;
using System.ComponentModel;
namespace demo_mah_wpf
{
public class TaskViewModel : ObservableCollection<Task>, INotifyPropertyChanged, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
private ObservableCollection<Task> _Tasks;
public ObservableCollection<Task> Tasks
{
get { return _Tasks; }
set
{
_Tasks = value;
//CollectionChanged(this, new PropertyChangedEventArgs());
}
}
private static object _lock = new object();
public TaskViewModel() : base()
{
//this.ctxFactory = ccf; //DB Context, using DI
this.Tasks = new ObservableCollection<Task>();
BindingOperations.EnableCollectionSynchronization(Tasks, _lock);
// use async in .net framework 4.7.2
// otherwise, complie error: async streams is not available in 7.3
// https://bartwullems.blogspot.com/2020/01/asynchronous-streams-using.html
System.Threading.Tasks.Task.Run(async () => {
//await foreach (Task card in GetAllCards())
//{
// this.Tasks.Add(card);
//}
var getTaskResult = this.GetAllTasks();
if(getTaskResult != null && getTaskResult.Result != null)
{
var getTaskResultList = getTaskResult.Result.ToList();
if (getTaskResultList.Count != 0)
{
foreach (Task task in getTaskResultList)
{
Add(new Task(task.TaskName, task.Description, task.Priority, task.TaskType));
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
}
//Add(task);
}
}
});
}
private async System.Threading.Tasks.Task<IEnumerable<Task>> GetAllTasks()
{
List<Task> Items = new List<Task>();
for (int i = 1; i <= 3; i++)
{
await System.Threading.Tasks.Task.Delay(1000);
Items.Add(new Task("Development", "Write a WPF program", 2, TaskType.Home));
}
return Items;
}
}
}
I tried implement INotifyPropertyChanged
, INotifyCollectionChanged
for TaskViewModel
. but still not work.
In debug, the program was ran the code in TaskViewModel
.
thanks to Clemens, you help better understand WPF, the source was uploaded for who want it, please free feel to checkout https://github.com/dolphinotaku/demo-wpf/tree/812bd075487dd69a31c691b63fea6eef5931948f
答案1
得分: 1
以下是更新 ListBox 的视图模型的完整和最简单的示例。
XAML 只需将视图的 DataContext
设置为视图模型类的实例,并为 ListBox 的 ItemsSource
属性分配适当的绑定:
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemData}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
视图模型声明了一个只读的 ObservableCollection 属性,并设置了一个定时器,定期更新集合:
public class Item
{
public string ItemData { get; set; }
}
public class ViewModel
{
public ObservableCollection<Item> Items { get; }
= new ObservableCollection<Item>();
private readonly DispatcherTimer timer = new DispatcherTimer();
private readonly Random random = new Random();
public ViewModel()
{
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += TimerTick;
timer.Start();
}
private void TimerTick(object sender, EventArgs args)
{
Items.Clear();
Items.Add(new Item { ItemData = $"Item Data: {random.Next(100)}" });
Items.Add(new Item { ItemData = $"Item Data: {random.Next(100)}" });
Items.Add(new Item { ItemData = $"Item Data: {random.Next(100)}" });
Items.Add(new Item { ItemData = $"Item Data: {random.Next(100)}" });
}
}
所有 UI 更新都由 ObservableCollection 对象触发。
如果您现在想要异步执行一些耗时任务,可以将 Tick 事件处理程序方法声明为 async
并在更新集合之前使用 await
等待任务。
英文:
Here is a complete and most simple example of updating a ListBox cyclically by a view model.
The XAML simply sets the DataContext
of the view to an instance of a view model class and assigns an appropriate Binding to the ItemsSource
property of a ListBox:
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemData}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
The view model declares a read-only ObservableCollection property and sets up a timer that cyclically updates the collection:
public class Item
{
public string ItemData { get; set; }
}
public class ViewModel
{
public ObservableCollection<Item> Items { get; }
= new ObservableCollection<Item>();
private readonly DispatcherTimer timer = new DispatcherTimer();
private readonly Random random = new Random();
public ViewModel()
{
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += TimerTick;
timer.Start();
}
private void TimerTick(object sender, EventArgs args)
{
Items.Clear();
Items.Add(new Item { ItemData = $"Item Data: {random.Next(100)}" });
Items.Add(new Item { ItemData = $"Item Data: {random.Next(100)}" });
Items.Add(new Item { ItemData = $"Item Data: {random.Next(100)}" });
Items.Add(new Item { ItemData = $"Item Data: {random.Next(100)}" });
}
}
All UI updates are triggered by the ObservableCollection object.
If you now want to perform some time-consuming task asnychronously, you may declare the Tick event handler method async
and await
the task before updating the collection.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论