Изменить свойство на основе таймера в Silverlight - PullRequest
2 голосов
/ 03 марта 2010

Предположим, у меня есть класс, который выглядит так:

class Sample
{
    public string Value { get; set; }
    public DateTime Begin { get; set; }
    public DateTime End { get; set; }
}

Я хочу отобразить список Sample экземпляров, каждый из которых меняет цвет, когда текущее время проходит Begin, а затем снова меняет цвет, когда текущее время проходит End.

Например, скажем, у меня есть DataGrid, содержащий Sample вроде этого:

dataGrid1.ItemsSource = new List<Sample> {
    { Value="123",
      Begin=DateTime.Parse("10:00"),
      End=DateTime.Parse("11:00") } };

Как бы получить строку, показывающую "123", которая будет красной в 9:59, станет желтой в 10:00 и станет красной в 11:00?

РЕДАКТИРОВАТЬ: Одна вещь, которая меня особенно беспокоит, это взрыв таймера. Если у меня будет 10 000 семплов, будет ли проблемой иметь таймеры 10 КБ (или 20 КБ)? Что делать, если у меня есть образцы 1M? Я думаю, что было бы лучше сделать таймеры для каждой строки сетки, а не для выборки.

Ответы [ 2 ]

4 голосов
/ 03 марта 2010

Делая это:

MainPage.xaml

<UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"  x:Class="ColorGridRow.MainPage"
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:ColorGridRow" mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
    <data:DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False">
        <data:DataGrid.Columns>
            <data:DataGridTemplateColumn>
                <data:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid Background="{Binding RowBackground}">
                            <TextBlock Text="{Binding Value}"/>
                        </Grid>
                    </DataTemplate>
                </data:DataGridTemplateColumn.CellTemplate>
            </data:DataGridTemplateColumn>
            <data:DataGridTemplateColumn>
                <data:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid Background="{Binding RowBackground}">
                            <TextBlock Text="{Binding Begin}"/>
                        </Grid>
                    </DataTemplate>
                </data:DataGridTemplateColumn.CellTemplate>
            </data:DataGridTemplateColumn>
            <data:DataGridTemplateColumn>
                <data:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid Background="{Binding RowBackground}">
                            <TextBlock Text="{Binding End}"/>
                        </Grid>
                    </DataTemplate>
                </data:DataGridTemplateColumn.CellTemplate>
            </data:DataGridTemplateColumn>
        </data:DataGrid.Columns>
    </data:DataGrid>
</Grid>

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Media;

namespace ColorGridRow
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            DataContext = new List<Sample>
                {
                    new Sample("1", DateTime.Now + TimeSpan.FromSeconds(1), DateTime.Now + TimeSpan.FromSeconds(3)),
                    new Sample("2", DateTime.Now + TimeSpan.FromSeconds(2), DateTime.Now + TimeSpan.FromSeconds(4)),
                    new Sample("3", DateTime.Now + TimeSpan.FromSeconds(3), DateTime.Now + TimeSpan.FromSeconds(5)),
                };
        }
    }

    public class Sample : INotifyPropertyChanged
    {
        private SolidColorBrush _savedRowBackground;
        private SolidColorBrush _rowBackground;

        public string Value { get; private set; }
        public DateTime Begin { get; private set; }
        public DateTime End { get; private set; }

        public SolidColorBrush RowBackground
        {
            get { return _rowBackground; }    
            set
            {
                _rowBackground = value;
                NotifyPropertyChanged("RowBackground");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        private void NotifyPropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public Sample(string value, DateTime begin, DateTime end)
        {
            Value = value;
            Begin = begin;
            End = end;
            RowBackground = new SolidColorBrush(Colors.Red);

            Observable.Timer(new DateTimeOffset(begin)).Subscribe(_ =>
            {
                _savedRowBackground = _rowBackground;
                RowBackground = new SolidColorBrush(Colors.Yellow);
            });

            Observable.Timer(new DateTimeOffset(end)).Subscribe(_ => RowBackground = _savedRowBackground);

        }
    }
}
1 голос
/ 03 марта 2010

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

Указывает на изменение состояния

Сначала вам понадобится какой-то способ предупредить пользовательский интерфейс об изменении статуса 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;
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...