ObservableCollection绑定到带有异步数据的DataTemplate

huangapple go评论78阅读模式
英文:

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:

&lt;Window.DataContext&gt;
    &lt;local:ViewModel/&gt;
&lt;/Window.DataContext&gt;
&lt;Grid&gt;
    &lt;ListBox ItemsSource=&quot;{Binding Items}&quot;&gt;
        &lt;ListBox.ItemTemplate&gt;
            &lt;DataTemplate&gt;
                &lt;TextBlock Text=&quot;{Binding ItemData}&quot;/&gt;
            &lt;/DataTemplate&gt;
        &lt;/ListBox.ItemTemplate&gt;
    &lt;/ListBox&gt;
&lt;/Grid&gt;

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&lt;Item&gt; Items { get; }
        = new ObservableCollection&lt;Item&gt;();

    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 = $&quot;Item Data: {random.Next(100)}&quot; });
        Items.Add(new Item { ItemData = $&quot;Item Data: {random.Next(100)}&quot; });
        Items.Add(new Item { ItemData = $&quot;Item Data: {random.Next(100)}&quot; });
        Items.Add(new Item { ItemData = $&quot;Item Data: {random.Next(100)}&quot; });
    }
}

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.

huangapple
  • 本文由 发表于 2023年7月3日 15:57:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76602837.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定