Чего не хватает в этом обновленном интерфейсе через диспетчер / привязку данных - PullRequest
0 голосов
/ 25 мая 2018

У меня есть простое окно 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"));
        });
    }
}

Конечно, проблема в том, что пользовательский интерфейс не обновляется в цикле.Чего мне не хватает?
Как мне выполнить обновление пользовательского интерфейса?

Ответы [ 3 ]

0 голосов
/ 25 мая 2018

ObservableCollection уже вызывает событие PropertyChanged при его изменении.Вам также не нужно вызывать событие в потоке пользовательского интерфейса.

Ваша модель может быть такой простой, как:

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 - это обработчик кликов, который означает, что он работает в потоке пользовательского интерфейса.Это означает, что он будет блокировать поток пользовательского интерфейса до завершения цикла.После завершения цикла пользовательский интерфейс будет обновлен.

Если вы хотите, чтобы пользовательский интерфейс оставался отзывчивым во время цикла, используйте 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, замените старую и вызовите событие 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 заменяется и не может вызывать какие-либо события самостоятельно

0 голосов
/ 25 мая 2018

Это DoEvents вещь, здесь :

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

или даже, возможно, лучше https://stackoverflow.com/a/11899439/138078.

Конечно, тест должен быть написан по-другомутаким образом, который не требует этого.

0 голосов
/ 25 мая 2018

Причиной, по которой пользовательский интерфейс не обновляется в цикле, является вызов Dispatcher.BeginInvoke.Это помещает новый DispatcherOperation в очередь диспетчера.Но ваш цикл уже является диспетчерской операцией, и он продолжается в потоке Dispatcher.Таким образом, все операции, которые вы ставите в очередь, будут выполнены после завершения операции цикла.

Может быть, вы хотели запустить StartTest в фоновом потоке?Затем пользовательский интерфейс обновится.

Кстати, не блокируйте поток Dispatcher с помощью Thread.Sleep.Он не позволяет Dispatcher выполнять свои действия максимально плавно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...