繁体   English   中英

Winui3 桌面; ObservableCollection,在属性更改时更新 UI? 从不同线程更新

[英]Winui3 Desktop; ObservableCollection, updating UI when property changes? Updating from different thread

这是一个小型测试应用程序,用于尝试从我的主应用程序中找出这个问题。 我先贴代码。

XAML:

<Window
    x:Class="ThreadTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ThreadTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Border BorderBrush="Black" BorderThickness="2" Grid.Row="0"/>

        <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0">
            <Button Click="{x:Bind ViewModel.AddPosition}" Content="Add To List" Margin="5"/>
            <TextBlock Text="{x:Bind ViewModel.OutputString, Mode=OneWay}" Margin="5"/>
            
            <ListView ItemsSource="{x:Bind ViewModel.PositionCollection, Mode=OneWay}" Margin="5">
                <ListView.HeaderTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>

                            <Border BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
                                <TextBlock Text="ID" Margin="5,0,0,0" FontWeight="Bold"/>
                            </Border>
                            <Border Grid.Column="1" BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
                                <TextBlock Text="Place" Margin="5,0,0,0" FontWeight="Bold"/>
                            </Border>
                        </Grid>
                    </DataTemplate>
                </ListView.HeaderTemplate>
                
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="local:PositionModel">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Text="{x:Bind Path=ID, Mode=OneWay}"/>
                            <TextBlock Grid.Column="1" Text="{x:Bind Path=Place, Mode=OneWay}" />
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            
        </StackPanel>        
    </Grid>
   
</Window>

视图模型(MainViewModel.cs):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.UI.Dispatching;
using Windows.UI.Core;
using Windows.ApplicationModel;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace ThreadTest {
    public class MainViewModel : BindableBase, INotifyPropertyChanged {
        private String outputString = "Empty";

        public MainViewModel() {

        }

        public String OutputString {
            get { return outputString; }
            set { SetProperty(ref outputString, value); }
        }

        private Random _random = new Random();  
        private int _id = 0;
        
        private ObservableCollection<PositionModel> _positioncollection = new();
        public ObservableCollection<PositionModel> PositionCollection {
            get { return _positioncollection; }
            set { SetProperty(ref _positioncollection, value); }
        }

        public async void AddPosition() {
            Progress<PositionModel> progress = new();
            progress.ProgressChanged += Progress_ProgressChanged;

            // Increase id for each new position added.
            _id++;
            // Setup/
            var _position = new PositionModel {
                ID = _id,
                Place = _random.Next(1, 1000), // Get a random starting point.
            };

            PositionCollection.Add(_position);            
            PositionsClass positionsClass = new(ref _position, progress);

            await Task.Run(() => { positionsClass.Start(); });
        }

        private void Progress_ProgressChanged(object sender, PositionModel e) {
            // This is so I can see that the thread is actually running.
            OutputString = Convert.ToString(e.Place);
        }
    }
}

可绑定基地.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;


namespace ThreadTest {
    public class BindableBase : INotifyPropertyChanged {

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetProperty<T>(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null) {
            if (Equals(originalValue, newValue)) {
                return false;
            }

            originalValue = newValue;
            OnPropertyChanged(propertyName);

            return true;
        }

    }
}

位置模型.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;


namespace ThreadTest {
    public class PositionModel {

        /*
        //Impliment INotifyPropertyChanged up above if using this.
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetProperty<T>(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null) {
            if (Equals(originalValue, newValue)) {
                return false;
            }

            originalValue = newValue;
            OnPropertyChanged(propertyName);

            return true;
        }

        private int _id = 0;
        public int ID {
            get { return _id; }
            set { SetProperty(ref _id, value); }
        }

        private int _place = 0;
        public int Place {
            get { return _place; } 
            set { SetProperty(ref _place, value); }
        }
        */

        public int ID { get; set; }
        public int Place { get; set; }  
    }
}

PositionsClass.cs:

using Microsoft.UI.Dispatching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadTest {
    public class PositionsClass {
        private IProgress<PositionModel> _progress;
        private PositionModel _position;

        public PositionsClass(ref PositionModel position, IProgress<PositionModel> progress) {
            _progress = progress;
            _position = position;
        }

        public void Start() {

            StartFakePosition();
        }

        private void StartFakePosition() {

            // Just using a quick loop to keep the numbers going up.
            while (true) {
                _position.Place++;

                // Send position back.
                _progress.Report(_position);

                Thread.Sleep(100);
            }
        }
    }
}

所以基本上您将单击“添加到列表”按钮,这将创建 PositionsClass positionsClass,创建并填充 PositionModel _position,创建一个 ObservableCollection PositionCollection(绑定到 xaml 上的列表视图),然后将 class 分离到它自己的线程中。 class 将获取_position 并增加其.Place,然后将progress.report _position 返回给主线程。

现在我想弄清楚如何获取 PositionCollection(ObservableCollection 的)来更新 lisview ui。 我订阅了 progress.ProgressChanged 并更新了 OutputString 以确保 class 实际上正在运行并递增,这确实有效。

我已经尝试了我在 web 上发现的各种东西,包括不同的继承 ObversableCollection 方法,这些方法都不起作用,或者我误解了它们。

我认为在 PositionModel.cs 上实现 INotifyPropertyChange 本身会起作用(注释掉的代码),但这样做会引发跨线程错误。 我想这是因为单独线程上的 positionsClass 正在更新。导致跨线程错误的地方?

任何人都可以帮助解释如何让 ObservableCollection 在我上面的示例中的属性更改时更新 ui 吗? 谢谢,在我的主应用程序中,我将在一个单独的线程上更新很多属性。 而不仅仅是这个例子中的 2。 这就是为什么我认为将整个 model 发送回 progress.report 会更容易。

我想我已经想通了。 首先,我在上面的 PositionModel.cs 代码(注释掉的部分)上启用了 INotifyProperty。 然后我添加:

        private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
        public CoreDispatcher Dispatcher { get; }

到 MainViewModel,修改 AddPosition 为:

        public async void AddPositionDispatcher() {

            // Increase id for each new position added.
            _id++;
            // Setup/
            var _position = new PositionModel {
                ID = _id,
                Place = _random.Next(1, 1000), // Get a random starting point.
            };

            PositionCollection.Add(_position);
            PositionsClassDispatcher positionsClassDispatcher = new(_position, _dispatcherQueue);

            await Task.Run(() => { positionsClassDispatcher.Start(); });
        }

我将 DispatcherQueue 发送到新修改的 PositionsClassDispatcher.cs:

using Microsoft.UI.Dispatching;
using System.Threading;


namespace ThreadTest {
    internal class PositionsClassDispatcher {
        private PositionModel _position;
        DispatcherQueue _queue;

        public PositionsClassDispatcher(PositionModel position, DispatcherQueue dispatcherQueue) {
            _queue = dispatcherQueue;
            _position = position;
        }

        public void Start() {
            StartFakePosition();
        }

        private void StartFakePosition() {

            // Just using a quick loop to keep the numbers going up.
            while (true) {  
              _queue.TryEnqueue(() => {
                    _position.Place++;
                });
                Thread.Sleep(100);
            }
        }
    }
}

这将采用 DispatcherQueue 并使用 TryEnqueue 来更新 _position.Place。 ObvservableCollection 现在可以在更新属性时正确更新 UI。 同时更新 XAML 以使用新的 AddPositionDispatcher()。

此外,必须使用 DispatcherQueue 而不是 Dispatcher,因为 WinUI3 似乎不再有 Dispatcher。

Window.Dispatcher 属性

Window。Dispatcher 可能会在未来的版本中更改或不可用。 请改用 Window.DispatcherQueue。

这在试图解决这个问题时引起了很多问题,因为那里的很多信息都是基于 Dispatcher 而不是 DispatcherQueue。

希望这可以帮助遇到问题的其他人。

暂无
暂无

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

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