0

Всем привет)

Суть вопроса: в базе данных имеется набор данных, вроде срока жизни продукта и даты начала этого срока. В форме хочется отображать оставшееся время до истечения срока жизни, причем отображать до секунд. Классическая схема MVVM подразумевает изменение вью модели. Выходит что надо каждую секунду обновлять по таймеру модель представления. Вот только набор данных в БД может содержать теоретически у клиента до миллионов записей и пересчитывать все модели при том что отображаются только 20-30 из них выглядит расточительством расчетных мощностей.

Мне кажется более разумным реализовывать на вьюхе обновление и производить перерасчет таймера жизни на самой форме. Все что надо попросить представление обновить получение данных в отдельных контролах по таймеру из модели представления по биндингу. Отображаем таймер допустим через конвертер, для этого в конвертере у текущего элемента из списка получаем дату начала жизни, срок жизни и по текущему времени высчитываем остаток, формируем строку таймера для отображения. Если просто привязаться к VM, то такой конвертер один раз рассчитает время жизни и поскольку обновления от VM нет заморозит обновление контрола. А хочется периодически подергивать из вьюхи вызов данных. Мы вроде как используем все тот же биндинг, то есть другой связи между ViewModel и View нет, и не нарушаем паттерн.

Буду благодарен за любые идеи)

P.S.: на деле расчет отображать таймер или нет, как его считать и т.д. все зависит от ряда параметров в модели и заложенной разработчиками логики.

KingPeas
  • 2,695
  • Так, в базе данные с датой старта и сроком как я понял вам надо их все вывести на экран, с обратным отсчетом, так? до миллионов записей - вы понимаете, что такое выводить разом бессмысленно, а значит нужны страницы. Я бы вам предложил следующее решение, то есть, вы делите данные по страницам (например 10 объектов за раз), при выводе страницы вы обращаетесь к базе, берете дату и все остальное, делаете VM с таймером и запускаете отсчёт, по завершению клиент повторно обращается к базе (если надо). – EvgeniyZ Apr 16 '21 at 14:26
  • У нас можно сказать табличный вывод. Для того чтобы определить только актуальные записи для обновления необходимо получать данные с представления, что фактически нарушает принципы MVVM. – KingPeas Apr 19 '21 at 04:15
  • А при чем тут это? Табличный, не табличный, без разницы. Сама то логика, что я вам предложил не меняется, ей без разницы какой у вас вид, хоть зигзагами выводите на экран, без разницы, это все равно будет в VM коллекция текущих объектов для отображения, которую вы заполняете по определенной логике. – EvgeniyZ Apr 19 '21 at 10:27
  • Я бы сделала в VM свойство для времени (например TimerProperty), затем так же в VM сделать асинхронную функцию, которая при создании VM запускается, которая каждую секунду прибавляет единицу к TimeProperty), а после привязала бы поле к TextBlock. Будет обновляться не VM, а её свойство. – A B Dec 23 '21 at 12:39
  • @AB и чем же это отличается от обновления VM? Ведь по сути когда у нас заполняется VM содержащая список продуктов, мы в итоге для каждого создаем свою VM, выходит на каждой создается свой таймер и обновляет VM собственными силами. Уж лучше тогда общий таймер для всех продуктов и проводить обновления, что сейчас и реализовано – KingPeas Dec 24 '21 at 09:33
  • @KingPeas, может тогда в классе продукта прописать асинхронный метод, который будет запускаться при создании объекта и свойство, которое в этом асинхронном методе обновляется? – A B Dec 26 '21 at 20:10
  • Так и сделано. Существует миллион записей в базе. Контроллер каждую секунду пробегается по ним и пересчитывает одно поле с таймером для отображения с миллиона этих записей в отдельном потоке. Соотвественно весь миллион послылает уведомления о том что он обновился. Реагируют те что сейчас на экране. Фактически же в базе есть значение дата начала использования и время жизненного цикла. А в расчетах для пользователя обновляется вычисляемое поле оставшееся время. И расчет этот надо запускать принудительно. Только вопрос почему не делать это из вью, только для видимых полей? – KingPeas Dec 27 '21 at 03:37

2 Answers2

0

Я нашел на просторах сети ответ как вызвать обновление в форме. Осталось понять на сколько это разумно:

((ComboBox)sender).GetBindingExpression(ComboBox.ItemsSourceProperty).UpdateTarget();
KingPeas
  • 2,695
  • Если это событие в VM слое или даже может в M, то вы серьезно нарушаете MVVM, ибо по его правилам другие слои не должны знать что либо про View слой, как и View не должен знать что либо про другие слои. – EvgeniyZ Apr 16 '21 at 14:28
  • Ну почему нарушает то? Смотрите, если таймер запускать непосредственно на View, при этом пробегаемся по дереву видимых объектов и в каждом вызываем обновление выражения через Binding. Наша View знает про VM не больше чем раньше. В итоге мы пересчитываем не 1млн Items в списке данных, а лишь 20-30 элементов которые актуально отображаются на экране – KingPeas Apr 19 '21 at 04:19
  • View - это слой UI, то есть цвет, шрифт, анимации, разный вид отображения данных и так далее. В нем не должно быть какой-либо логики по работе с данными. А теперь смотрим что вы написали: таймер запускать непосредственно на View - то есть, вы добавляете некую логику "данных" в View слой, ок, предположим вам надо в будущем узнать, например в VM сколько осталось до конца, как будете получать это значение? Вы будете дергать View слой, что уже является нарушением. пробегаемся по дереву видимых объектов - то есть пробегаемся по данным, то есть V слой работает с данными VM, так? – EvgeniyZ Apr 19 '21 at 10:23
  • И все же я не согласен. Идея не в том чтобы данные во View обрабатывать, мы только инициируем перерисовку нужных контролов, чтобы UI обновлялся с переиодичностю. Фактически таймер это представление для вывода информации в удобном виде, которые для удобства должны периодически обновлять свое представление. При этом сами данные которые хранятся в модели никак не изменяются. Пересчитывать данные во VM для их визуализации кажется не разумным. – KingPeas Apr 19 '21 at 18:51
  • Пробегаясь по дереву видимых объектов мы тем самым только активные контролы просим обновиться, никак не трогая данные, не обновляем и не изменяем их... Если так подумать то очень похоже на обновление контента по анимации) Надо подумать в этом направлениии, может это будет более правильным вариантом. – KingPeas Apr 19 '21 at 19:12
  • мы только инициируем перерисовку нужных контролов - чтобы что? Чтобы отобразить новые данные, верно? Почему тогда сами данные не могут оповестить о своем обновление (INPC)? кажется не разумным - я вам уже давал задачку, вот предположим вам надо узнать в VM об обновление, или узнать сколько осталось там до конца "таймера", что, будете повторно подсчитывать все? Или лезть в V слой за данными? Или все же лучше сделать свойство в VM и пусть оно за это все отвечает. никак не трогая данные, не обновляем и не изменяем их - зачем тогда обновление, ради чего? Данные старые ведь старые. – EvgeniyZ Apr 19 '21 at 20:29
  • Вообще, вот задайте себе вопрос, а есть хоть один контрол, который имеет внутри таймер, который что-то подсчитывает там "чисто для V конечно" и не затрагивает как-либо VM/M? Я вот что-то не припоминаю подобных. Они либо статичны, либо обновляются только тогда, когда их просят данные, не более. Всяким отсчетам, ну не знаю, не место в View слое. Вообще, MVVM это лишь набор рекомендуемых правил, делайте там проект уже как вашей душе будет угодно, хоть все в контролы запихивайте, это ваш проект. Ну а я все же буду придерживаться такому подходу, где контролы максимально отделены от основной логики. – EvgeniyZ Apr 19 '21 at 20:34
  • Я под вопросом ссылку давал на пример, там каждый объект самостоятельный, я в любой момент могу взять этот объект и прочесть свойство TimeLeft, которое мне скажет сколько осталось до конца, это будет без каких-либо повторных подсчетов, без загрузки данных повторно. Я могу с этим свойством делать что угодно, например, могу привязать его в другом окне, без необходимости повторной инициализации и запуска таймера. А ваш View, как вы поступите, если вам надо будет отобразить тоже самое в другом окне? Будете использовать свой контрол, который повторно прочитает данные и запустит таймер что ли? – EvgeniyZ Apr 19 '21 at 20:45
  • Мы не отображаем новые данные и ничего не пересчитываем. Мы только меняем их представление в форме. как прогресс бар или строчка с текстом. Исходные данные не меняются: дата начала и срок жизни всегда одни. Изменяется способ их представления пользователю. Вы предлагаете существующий подход. Для того чтобы показать сколько времени осталось рассчитать это во VM и по таймеру обновлять каждую секунду весь пул данных. Мне кажется это избыточным. Какие записи активны знает только View. Что в них рисовать решает Binding+Converter. Вы также Enum переводите в строку или иконку. – KingPeas Apr 20 '21 at 03:26
  • Почему нельзя через тот же конвертер имея на входе дату начала и длительность рассчитать оставшийся таймер? Данные во VM у нас не меняются и сам таймер меняться не будет. Если обновят исходные данные и отображение пересчитается. По факту кроме отображения таймера у этих данных нет другой функции, вот я и не вижу смысла пересчитывать миллионы записей каждую секунду. Мы только готовим данные для UI в расчете. Так пусть расчет делается через механизм биндинга чуть чаще при помощи той же анимации. – KingPeas Apr 20 '21 at 03:37
  • Пусть анимация раз в секунду будет просить контрол загрузить данные из VM(которые остались теми же), но в момент обновления внутри конвертера исходя из текущего времени пересчитывает представление данных. При этом VM не меняется по факту. – KingPeas Apr 20 '21 at 04:05
  • не вижу смысла пересчитывать миллионы записей каждую секунду. - у вас и не должно быть миллиона записей на экране. Как бы вы не отображали такое кол-во, это будет "избыточным" и очень сильно нагружать систему, да и очень сильно неудобно будет для пользователя, ибо ему за раз миллион записей нафиг не сдалось, раз прокрутит и уже потерялся в этой гуще информации, а это уже не очень хороший UX. Я вам уже в самом начале сказал, что тут нужны страницы либо другие механизмы выдали информации порционально, а когда на экране у вас допустим 50 записей, то 50 таймеров для системы это пустяк. – EvgeniyZ Apr 20 '21 at 10:22
  • Пусть анимация раз в секунду будет просить контрол загрузить данные из VM - это не его ответственность, да и вы сами говорите что данные не обновляются, к чему тогда ему просить новые данные, если они по факту старые? Также вы мне не ответили на вопрос, как вы поступите, когда вам надо этот же "миллион записей" отобразить например в другом окне, какие ваши действия? Также будете повторно подсчитывать сколько осталось, запускать таймеры и все в этом духе? Не кажется вот это вот избыточным? Или что вы будете делать, если потребуется узнать сколько осталось до конца в VM слое? Опять считать? – EvgeniyZ Apr 20 '21 at 10:26
0

Может так?

Модель:

    public class Product : Notify
{
    public Product()
    {
        SecondTimer();
    }
    private int _id;
    private string _name;
    private DateTime _date;
    private string _subDate;
    private int _daysOfEnd;
public int Id { get { return _id; } set { _id = value; OnPropertyChanged(); } }
public string Name { get { return _name; } set { _name = value; OnPropertyChanged(); } }
public DateTime Date { get { return _date; } set { _date = value; OnPropertyChanged(); } }
public int DaysOfEnd { get { return _daysOfEnd; } set { _daysOfEnd = value; OnPropertyChanged(); } }
public string SubstractDate { get { return _subDate; } set { _subDate = value; OnPropertyChanged(); } }
public DateTime EndDate { get { return Date.AddDays(DaysOfEnd); } }

private async void SecondTimer()
{
    while (true)
    {
        var time = EndDate.Subtract(DateTime.Now);
        SubstractDate = $"дней {time.Days} Часов {time.Hours} Минут {time.Minutes} Секунд {time.Seconds}";
        await Task.Delay(1000);
    }
}

public static List<Product> CreateList()
{
    var products = new List<Product>();
    products.Add(new Product { Date = DateTime.Parse("20/12/2021 10:00:00"), Name = "Бананы", Id = 1, DaysOfEnd=7});
    products.Add(new Product { Date = DateTime.Parse("21/12/2021 12:21:15"), Name = "Яблоки", Id = 2, DaysOfEnd=15});
    products.Add(new Product { Date = DateTime.Parse("22/12/2021 13:20:40"), Name = "Груши", Id = 3, DaysOfEnd=6});
    return products;
}

}

Вью модель:

    internal class ProductVM
{
    public ProductVM()
    {
        Products = Product.CreateList();
    }
    public List<Product> Products { get; set; }
}

Разметка:

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.Resources>
    <local:ConverterSubDate x:Key="Conv"/>
</Window.Resources>
<Grid>
    <ListView ItemsSource="{Binding Path=Products}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Path=Id}"/>
                    <TextBlock Text="{Binding Path=Name}"/>
                    <TextBlock Text="{Binding Path=Date, StringFormat='{}{0:dd/MM/yy hh:mm:ss}'}"/>
                    <TextBlock Text="{Binding Path=EndDate, StringFormat='{}{0:dd/MM/yy hh:mm:ss}'}"/>
                    <TextBlock Text="{Binding Source={x:Static sys:DateTime.Now}, StringFormat='{}{0:dd/MM/yy hh:mm:ss}'}"/>
                    <TextBlock Text="{Binding Path=SubstractDate}"/>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

Результат: введите сюда описание изображения

A B
  • 408
  • Объясните мне чем это принципиально отличается от обновления вью модели? Вы пересчитываете все модели и за счет уведомлений будут обновляться на вью только видимые. Вопрос то был именно в том что такая система уже реализована. Но на сколько разумно пересчитывать все миллионы записей вью модели если реально ты видишь только 10-20 из них и это расчетное поле, которое больше не имеет никакого важного значения. Я спрашивал не лучше ли принудительно видимые элементы просить себя пересчитать по таймеру на вью – KingPeas Dec 27 '21 at 03:28
  • @KingPeas тогда использовать постраничный вывод или отложенную загрузку. – A B Dec 28 '21 at 14:45