假设我有一个如下所示的类:
class Sample
{
public string Value { get; set; }
public DateTime Begin { get; set; }
public DateTime End { get; set; }
}
我想显示Sample
个实例列表,其中每个实例在当前时间超过Begin
时会改变颜色,然后在当前时间超过End
时再次更改颜色。
例如,假设我有一个包含Sample
类似的DataGrid:
dataGrid1.ItemsSource = new List<Sample> {
{ Value="123",
Begin=DateTime.Parse("10:00"),
End=DateTime.Parse("11:00") } };
如何在9:59将显示“123”的行变为红色,在10:00变为黄色,在11:00变为红色?
编辑:我特别关注的一件事是计时器爆炸。如果我有10,000个样本,那么拥有10k(或20k)定时器会有问题吗?如果我有1M样品怎么办?我认为每个网格行而不是每个样本都可以制作定时器。答案 0 :(得分:4)
通过这样做:
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 :(得分:1)
可能有很多方法可以做到这一点,而真实应用中的其他因素可能会影响我在下面列出的方法是否适合您的应用。
表明状态变化
首先,您需要一些方法来提醒用户界面Sample
状态发生变化,它会在一段时间内处于范围内,然后会超出范围。您可以将此状态作为Sample
类型的属性公开。您可以通过实现INotifyPropertyChanged
接口来通知UI。以下是您的课程在实施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
属性的值为true。 (我会回到那里)。
将布尔值转换为画笔
下一个问题是您想要更改项目的颜色。因此,我们想要将Foreground
的{{1}}属性绑定到TextBlock
的{{1}}属性。所以我们需要一个NowInRange
: -
TimedSample
将这些放在一起的一些XAML
现在我们只需将此转换器放在资源字典中,我们就可以将其全部连接起来。
下面的Xaml假设已将IValueConverter
个对象的列表分配给Usercontrol的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");
}
}
属性。
TimedSample
勾选
现在需要的是一些使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
被实例化,它将永远不会被GC释放和收集。它将永远被定时器的Tick事件引用,更糟糕的是,尽管没有在其他任何地方使用,但仍将继续执行timer_Tick中的代码。
Silverlight Toolkit以 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;
}
类的形式为此提供了简洁的解决方案。 Beat Kiener关于它的博客,并在Simple Weak Event Listener for Silverlight中包含其代码。有了这个,TimedSample
构造函数可能如下所示: -
WeakEventListener
当UI不再引用TimedSample或GC可以收集它的任何其他地方时。当下一个Tick事件触发时,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;
}
,使得实例fo WeakEventListener
本身也可用于垃圾回收。
我已经开始了,所以我将完成
这个答案已经结束了很长时间,很抱歉,但看到它,我可能会给你我上面列出的Xaml使用的测试代码 - :
OnDetachAction