Связывание с зависящими от времени свойствами
Некоторое время назад я написал небольшое виджет-приложение, которое должно было отслеживать задачи, каждая задача имела конечный срок, указанный как DateTime
, теперь, если вы хотите отобразить, сколько времени осталось до крайнего срока, вы может привязываться к "виртуальному" (* проклинает свойство virtual
keyword *) следующим образом:
public TimeSpan TimeLeft
{
get { return Deadline - DateTime.Now; }
}
Очевидно, что теоретически это свойство меняет каждый тик, и вы хотите время от времени обновлять свой интерфейс (например, периодически выкачивая событие PropertyChanged
для этого свойства).
Назад, когда я написал виджет, каждую минуту обновлял весь список задач, но это вряд ли идеально, поскольку, если пользователь взаимодействует с каким-либо элементом (например, введя TextBox, который привязан к Comments
-property), который будет быть жестко перепутаны и обновления источника теряются.
Итак, что может быть лучшим подходом к обновлению пользовательского интерфейса, если у вас есть такие зависящие от времени свойства?
(Я не использую это приложение, кстати, просто подумал, что это очень интересный вопрос)
Ответы
Ответ 1
Я думаю, что вы сказали в своем первом абзаце после того, как образец кода является единственным разумным способом сделать эту работу в WPF. Настройте таймер, который просто вызывает PropertyChanged
для свойства TimeLeft
. Интервал будет варьироваться в зависимости от вашего сценария (если вы говорите о недельном списке задач, вам, вероятно, нужно будет обновлять его всего 5 минут или около того. Если вы говорите список задач в течение следующих 30 минут, вам может потребоваться обновлять его каждую минуту или 30 секунд или что-то в этом роде.
Этот метод позволит избежать проблем, о которых вы упомянули при обновлении, поскольку затронуты привязки TimeLeft
. Если у вас есть миллионы этих задач, я думаю, что штраф за производительность будет довольно значительным. Но если у вас было всего несколько десятков или что-то, обновление этих привязок каждые 30 секунд или около того было бы довольно незначительной проблемой, верно?
Каждая возможность, о которой я могу думать, использует таймеры или анимации. Анимация будет слишком "тяжелой", когда вы добавляете задачи в список. А из сценариев таймера, один выше, кажется, самый чистый, самый простой и практичный. Наверное, просто сводится к тому, работает ли он или нет для вашего конкретного сценария.
Ответ 2
Таймер - единственный способ, о котором я могу думать. Поскольку это интересный вопрос, я поставлю мой .02. Я бы инкапсулировал его, делая что-то вроде этого:
public class CountdownViewModel : INotifyPropertyChanged
{
Func<TimeSpan> calc;
DispatcherTimer timer;
public CountdownViewModel(DateTime deadline)
: this(() => deadline - DateTime.Now)
{
}
public CountdownViewModel(Func<TimeSpan> calculator)
{
calc = calculator;
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timer_Tick;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs("CurrentValue"));
}
}
public TimeSpan CurrentValue
{
get
{
var result = calc();
if (result < TimeSpan.Zero)
{
return TimeSpan.Zero;
}
return result;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MyViewModel
{
public CountdownViewModel DeadlineCountdown { get; private set; }
public DateTime Deadline { get; set; }
public MyViewModel()
{
Deadline = DateTime.Now.AddSeconds(200);
DeadlineCountdown = new CountdownViewModel(Deadline);
}
}
Затем вы можете напрямую привязать к DeadlineCountdown.CurrentValue
или создать CountdownView
. Вы можете переместить таймер на CountdownView
, если хотите. Вы можете использовать статический таймер, чтобы все они обновлялись одновременно.
Edit
Если Deadline
изменится, вам придется построить обратный отсчет следующим образом:
DeadlineCountdown = new CountdownViewModel(() => this.Deadline - DateTime.Now);
Ответ 3
Я прочитал ваш принятый ответ, но мне просто интересно... почему бы просто отключить привязки для этой конкретной задачи в режиме "Редактировать", чтобы вы не прерывались? Затем просто повторно включите эту привязку, когда вы либо закончите, или отмените свое редактирование? Таким образом, даже если ваш таймер обновляется каждую секунду, кому это нужно?
Как отключить их, не отсоединяя их (и, таким образом, сбросив их значение), просто определите логический флаг, затем во всех DP, которые вы хотите прервать, проверьте этот флаг в логике проверки. Если флаг является истинным, а объект DependencyObject, к которому он применяется, - это тот, который вы редактируете, заблокируйте изменение в DP.
Во всяком случае, это просто появилось у меня в голове. На самом деле это не проверено, но это должно быть легко попробовать.