Вероятно, существует несколько способов сделать это, и другие факторы в вашем реальном приложении могут повлиять на то, подходит ли подход, который я изложил ниже, вашему приложению.
Указывает на изменение состояния
Сначала вам понадобится какой-то способ предупредить пользовательский интерфейс об изменении статуса Sample
, он будет находиться в диапазоне в течение определенного периода времени, а затем выйдет за пределы диапазона. Вы могли бы выставить это состояние как свойство типа Sample
. Вы бы уведомили пользовательский интерфейс, внедрив интерфейс INotifyPropertyChanged
. Вот как выглядит ваш класс с INotifyPropertyChanged
: -
public class TimedSample : INotifyPropertyChanged
{
private string _Value;
public string Value
{
get { return _Value; }
set
{
_Value = value;
NotifyPropertyChanged("Value");
}
}
private DateTime _Begin;
public DateTime Begin
{
get { return _Begin; }
set
{
_Begin = value;
NotifyPropertyChanged("Begin");
}
}
private DateTime _End;
public DateTime End
{
get { return _End; }
set
{
_End = value;
NotifyPropertyChanged("End");
}
}
private bool _NowInRange;
public bool NowInRange
{
get { return _NowInRange; }
private set
{
_NowInRange = value;
NotifyPropertyChanged("NowInRange");
}
}
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Некоторый код, внутренний для TimeSample
, сделает значение свойства NowInRange
истинным, когда текущее время находится в пределах диапазона Begin
и End
. (Я вернусь к этому).
Преобразование логического значения в кисть
Следующая проблема заключается в том, что вы хотите изменить цвет элемента. Следовательно, мы бы хотели связать, скажем, свойство Foreground
TextBlock
со свойством NowInRange
TimedSample
. Итак, нам нужно IValueConverter
: -
public class BoolToBrushConverter : IValueConverter
{
public Brush FalseBrush { get; set; }
public Brush TrueBrush { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseBrush;
else
return (bool)value ? TrueBrush : FalseBrush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("This converter only works for one way binding");
}
}
Некоторые XAML, чтобы собрать это вместе
Теперь нам просто нужно поместить этот конвертер в словарь ресурсов, и мы можем подключить все это.
В приведенном ниже Xaml предполагается, что список TimedSample
объектов назначен свойству Usercontrol DataContext
.
<UserControl x:Class="SilverlightApplication1.ListBoxStuff"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SilverlightApplication1"
>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<local:BoolToBrushConverter x:Key="BoolToYellowAndRed" TrueBrush="Yellow" FalseBrush="Red" />
</Grid.Resources>
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"
Foreground="{Binding NowInRange, Converter={StaticResource BoolToYellowAndRed}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
Сделать это галочкой
Теперь необходим какой-то механизм, заставляющий свойство NowInRange
отображать свое значение в соответствующий момент времени. Опять же, есть несколько способов сделать это. Я буду использовать очень общее решение, основанное на DispatcherTimer
. В этом случае мы добавляем статически DispatcherTimer
экземпляр к классу TimedSample
. Это может выглядеть так: -
static readonly DispatcherTimer timer;
static TimedSample()
{
timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
timer.Start();
}
public TimedSample()
{
// Do not actually do this!
timer.Tick += timer_Tick;
}
private void timer_Tick(object sender, EventArgs e)
{
DateTime now = DateTime.Now;
if (NowInRange != (Begin < now && now < End))
NowInRange = !NowInRange;
}
Это будет хорошо работать, но есть проблема. Это приведет к утечке памяти, когда экземпляр TimedSample
будет создан, он никогда не будет освобожден и собран GC. На него всегда будет ссылаться событие Tick таймера, еще хуже будет продолжать выполнять код в timer_Tick, несмотря на то, что он больше нигде не используется.
Silverlight Toolkit имеет изящное решение в виде WeakEventListener
класса. Beat Kiener сообщает об этом в блоге и включает код для него в Simple Weak Event Listener для Silverlight . С этим на месте конструктор TimedSample
может выглядеть так: -
public TimedSample()
{
var weakListener = new WeakEventListener<TimedSample, DispatcherTimer, EventArgs>(this, timer);
timer.Tick += weakListener.OnEvent;
weakListener.OnEventAction = (instance, source, e) => instance.timer_Tick(source, e);
weakListener.OnDetachAction = (listener, source) => timer.Tick -= listener.OnEvent;
}
Когда TimedSample больше не ссылается на пользовательский интерфейс или где-либо еще, GC может собрать его. Когда происходит следующее событие Tick, WeakEventListener
обнаруживает, что объект пропал, и вызывает OnDetachAction
, что делает сам экземпляр fo WeakEventListener
также доступным для сборки мусора.
Я начал, так что я закончу
Этот ответ оказался довольно длинным, извините за это, но, учитывая, что это так, я могу также дать вам тестовый код, который я использовал для Xaml, перечисленного выше:
public partial class ListBoxStuff : UserControl
{
public ListBoxStuff()
{
InitializeComponent();
DataContext = GetTimedSamples(10, TimeSpan.FromSeconds(5));
}
IEnumerable<TimedSample> GetTimedSamples(int count, TimeSpan interval)
{
TimedSample sample = null;
for (int i = 0; i < count; i++)
{
sample = new TimedSample()
{
Value = String.Format("Item{0}", i),
Begin = sample != null ? sample.End : DateTime.Now,
End = (sample != null ? sample.End : DateTime.Now) + interval
};
yield return sample;
}
}
}