簡體   English   中英

通過調度程序/數據綁定在此更新UI中缺少什么

[英]What is missing in this update UI via dispatcher/databinding

我有一個簡單的WPF窗口,其中: Loaded="StartTest"

<Grid>
        <ListBox ItemsSource="{Binding Logging, IsAsync=True}"></ListBox>
</Grid>

在我后面的代碼中有StartTest方法:

LogModel LogModel = new LogModel();

void StartTest(object sender, RoutedEventArgs e)
{
    DataContext = LogModel;

    for (int i = 1; i<= 10; i++)
    {
       LogModel.Add("Test");
       Thread.Sleep(100);
    }
}

LogModel類是:

public class LogModel : INotifyPropertyChanged
{
    public LogModel()
    {
        Dispatcher = Dispatcher.CurrentDispatcher;
        Logging = new ObservableCollection<string>();
    }
    Dispatcher Dispatcher;

    public ObservableCollection<string> Logging { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    public void Add(string text)
    {
        Dispatcher.BeginInvoke((Action)delegate ()
        {
            Logging.Add(text);
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Logging"));
        });
    }
}

當然,問題在於UI不會在循環中更新。 我想念什么?
如何實現UI更新?

ObservableCollection已經引發了PropertyChanged事件。 您也不必在UI線程中引發事件。

您的模型可以很簡單:

class LogModel
{
    public ObservableCollection<string> Logging { get; } = new ObservableCollection<string>();

    public void Add(string text)
    {
        Logging.Add(text);
    }
}

您所需要做的就是將其設置為視圖的DataContext ,例如:

LogModel model = new LogModel();
public MainWindow()
{
    InitializeComponent();
    this.DataContext = model;
}

我假設StartTest是一個單擊處理程序,這意味着它在UI線程上運行。 這意味着它將阻塞 UI線程,直到循環完成。 循環結束后,UI將會更新。

如果希望UI在循環期間保持響應,請使用Task.Delay而不是Thread.Slepp,例如:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    for(int i=0;i<10;i++)
    {
        await Task.Delay(100);
        model.Add("Blah!");
    }
}

更新

您不需要使用ObservableCollection作為數據綁定源。 您可以使用任何對象,包括數組或列表。 在這種情況下,盡管您必須在代碼中引發PropertyChanged事件:

class LogModel:INotifyPropertyChanged
{
    public List<string> Logging { get; } = new List<string>();

    public event PropertyChangedEventHandler PropertyChanged;

    public void Add(string text)
    {
        Logging.Add(text);
        PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
    }
}

這將告訴視圖加載所有內容並再次顯示它們。 當您只想顯示從數據庫加載的數據而無需修改它們時,這非常好,因為這使映射實體到ViewModels變得容易得多。 在這種情況下,您僅需要在通過命令附加新ViewModel時更新視圖。

但是,當您需要更新冷卻時,這種方法效率高。 ObservableCollection實現INotifyCollectionChanged接口,該接口將為每次更改引發一個事件。 如果添加新項目,則只會渲染該項目。

另一方面,您應該避免在緊密循環中修改集合,因為它將引發多個事件。 如果您加載了50個新商品,請勿循環調用“ Add 50次”。 創建一個新的ObservableCollection,替換舊的ObservableCollection並引發PropertyChanged事件,例如:

class LogModel:INotifyPropertyChanged
{
    public ObservableCollection<string> Logging { get; set; } = new ObservableCollection<string>();

    public event PropertyChangedEventHandler PropertyChanged;

    public void Add(string text)
    {            
        Logging.Add(text);
        PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
    }

    public void BulkLoad(string[] texts)
    {
        Logging = new ObservableCollection<string>(texts);
        PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Logging"));
    }
}

仍然需要顯式實現,因為Logging屬性將被替換並且本身不會引發任何事件

循環中未更新UI的原因是對Dispatcher.BeginInvoke的調用。 這會將新的DispatcherOperation放置在調度程序隊列中。 但是您的循環已經是調度程序操作,它在Dispatcher的線程上繼續。 因此,您排隊的所有操作都將在循環操作完成后執行。

也許您想在后台線程上運行StartTest 然后,UI將更新。

順便說一句,不要用Thread.Sleep阻塞Dispatcher的線程。 它阻止Dispatcher盡可能順利地執行其工作。

這是DoEvents的事情, overhere

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}

甚至更好的https://stackoverflow.com/a/11899439/138078

當然,應該以不需要測試的不同方式編寫測試。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM