簡體   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