Изображение обновляется гораздо реже, чем состояние ViewModel. Можно ли это пофиксить?
Лет 10 не писал на WPF
, да и не сильно хорошо тогда его изучил. И тут понадобилось опять на нём написать задачку. Нагородил возможно велосипедов, хотелось бы разобраться, как сделать по уму.
В общем, в XAML
есть сторонний контрол для быстрого отображения графиков ScottPlot
в варианте для WPF
. Он довольно ограниченный и не умеет жить во ViewModel
, а только в code behind
. Я не разобрался, как можно обновить что-то в code behind
из View Model
, поэтому во View Model
делаются какие-то расчёты, а в code behind
висит DispatcherTimer
, по тикам таймера проверяется, выставил ли я флажок во ViewModel
, и если выставил, то происходит отрисовка в контроле. В целом это выглядит в упрощённом виде так:
Slider перемещается
|
V
обновляются данные ViewModel
|
V
отрисовка контрола в code behind
При этом реакция на движение слайдера выглядит какой-то дёрганной. Даже если таймер запускать вообще без задержки. Стал разбираться, добавил статус бар, в котором стал писать всякую статистику. Выяснилось, что расчёты выполняются меньше чем за 1мс, а рендер контрола в code behind
5мс. При этом, само обращение к расчётам происходит раз в 10 чаще, чем рендер. Если уменьшать задержку таймера, то соотношение рендеров к расчётам становится чуть лучше, но не намного. Либо сам таймер срабатывает тоже с какой-то задержкой, либо я ещё чего-то не понимаю.
Итак, вопросы:
- Может есть какой-то другой способ инициировать обновление контрола
ScottPlot.WPF
вcode behind
, без участия таймера? Как правильно послать сигнал изViewModel
вcode behind
? - Либо может как-то
DispatcherTimer
починить/заменить, чтобы он более часто срабатывал? Да хорошо ли это будет для приложения в целом - всё время проверять вcode behind
какой-то флаг без остановки? В то время, когда есть всякие биндинги казалось бы специально для этого. - Либо я вообще всю концепцию неправильно понимаю и нужно всё как-то совсем по-другому делать. Может есть какой-то пример правильного кода для
WPF
, который что-то графическое отрисовывает, реагируя наSlider
или другой подобный контрол в реальном времени без задержек?
Конечно, если бы этот контрол дружил с WPF
, то можно было бы его забиндить на поле во ViewModel
и он бы сам сразу отображался. Со ScottPlot.WPF
так, к сожалению, нельзя сделать, об этом прямо в документации написано, что это сознательное решение. Рисуем быстро, но с широким функционалом WPF
не дружим.
Ещё есть вариант в XAML
повесить Image
, а во ViewModel
через функции ScottPlot
делать рендер в bmp
и потом подставлять готовую картинку в этот Image
, я так ещё один контрол уже сделал. Но тогда теряются фичи ScottPlot.WPF
по зуму и скроллированию графиков. Может они мне и не нужны будут, эти фичи, но пока не хотел уходить совсем в этот вариант.
Да, и ещё конечно можно повесить в code behind
обработчики событий слайдера и делать все пересчёты там, а следом рендер. Но как-то не хотелось бы на такой низкий уровень совсем спускаться от биндингов без крайней необходимости.
Ответы (1 шт):
Зачем вообще рендер по таймеру?
Берёте INPC вьюмодель, реальзуете вызов события ProperyChanged
, подписываетесь на него в юзерконтроле, и на каждое изменение любого свойства тригерите рендер, сами из кода.
public class MyViewModel : INotifyPropertyChanged
{
private bool _myFlag;
public bool MyFlag
{
get => _myFlag;
set
{
if (_myFlag != value)
{
_myFlag = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Теперь подписаться
MyViewModel vm = new();
vm.PropertyChanged += OnVmPropertyChanged;
private void OnVmPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(MyViewModel.MyFlag))
{
Render();
}
}
Если нужно отследить добавление/удаление элементов ObservableCollection
, так же подпишитесь на CollectionChanged
у неё.
Только подписываясь на события не забудьте отписываться, когда контрол покидает визуальное дерево или диспозится.