WPF Storyboard中DiscreteObjectKeyFrame绑定值无法刷新问题求助
解决WPF中Storyboard绑定DateTime.Now无法自动刷新的问题
嘿,我来帮你搞定这个时钟更新的问题~你现在遇到的核心问题其实很好理解,咱们先理清楚原因,再给你两个可行的解决方案。
为什么当前方案不行?
DateTime.Now是个静态属性,它不会触发任何属性变更通知,而且XAML里的绑定只会在控件初始化的时候读取一次它的值。哪怕时间一秒一秒过去,这个绑定也不会主动去重新获取最新的时间,所以你的Storyboard重复运行时,显示的还是第一次加载时的那个时间。
方案一:用DispatcherTimer+ViewModel(推荐,完美替代原有Timer)
既然你想替换Timer,WPF里的DispatcherTimer天生就是为UI线程设计的,配合MVVM的属性通知机制,实现起来非常优雅:
第一步:创建时钟ViewModel
先写一个简单的ViewModel类,负责每秒更新时间并通知UI:
using System.ComponentModel; using System.Windows; using System.Windows.Threading; namespace WpfApp1 { public class ClockViewModel : INotifyPropertyChanged { private DateTime _currentTime; public DateTime CurrentTime { get => _currentTime; set { if (_currentTime != value) { _currentTime = value; OnPropertyChanged(nameof(CurrentTime)); } } } private readonly DispatcherTimer _updateTimer; public ClockViewModel() { _updateTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _updateTimer.Tick += (sender, e) => CurrentTime = DateTime.Now; _updateTimer.Start(); // 初始化显示当前时间 CurrentTime = DateTime.Now; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
第二步:给Window设置DataContext
在MainWindow的构造函数里,把ViewModel绑定到UI:
namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new ClockViewModel(); } } }
第三步:简化XAML代码
现在Label直接绑定ViewModel的属性就行,完全不需要Storyboard了:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="450" Width="800"> <Grid> <Label FontSize="40" Content="{Binding CurrentTime}" ContentStringFormat="hh:mm:ss"/> </Grid> </Window>
这个方案不仅替代了原来的Timer,还完全贴合WPF的绑定逻辑,代码清晰还好维护,绝对是首选~
方案二:如果坚持要用Storyboard(满足你的原有思路)
要是你一定要保留Storyboard的实现方式,那得先让绑定的时间值能实时更新。我们可以自定义一个带变更通知的静态时间服务:
第一步:创建静态时钟服务类
using System.ComponentModel; using System.Windows; using System.Windows.Threading; namespace WpfApp1 { public static class ClockService { private static readonly DispatcherTimer _updateTimer; private static DateTime _currentTime; static ClockService() { _currentTime = DateTime.Now; _updateTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _updateTimer.Tick += (sender, e) => { _currentTime = DateTime.Now; // 触发静态属性变更通知 StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(nameof(CurrentTime))); }; _updateTimer.Start(); } public static DateTime CurrentTime => _currentTime; public static event PropertyChangedEventHandler StaticPropertyChanged; } }
第二步:修改XAML绑定
把原来的DateTime.Now绑定换成我们自定义的静态属性,并且指定Mode=OneWay确保能接收变更通知:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp1" Height="450" Width="800"> <Grid> <Label FontSize="40" ContentStringFormat="hh:mm:ss"> <Label.Triggers> <EventTrigger RoutedEvent="Label.Loaded"> <BeginStoryboard> <Storyboard RepeatBehavior="Forever"> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Content" Duration="0:0:1"> <DiscreteObjectKeyFrame Value="{Binding Source={x:Static local:ClockService.CurrentTime}, Mode=OneWay}" KeyTime="0:0:0" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Label.Triggers> </Label> </Grid> </Window>
这里把Storyboard的Duration改成1秒,这样每次重复运行时都会重新读取最新的时间值,配合静态属性的变更通知,就能实现每秒刷新了。
内容的提问来源于stack exchange,提问作者Vimal CK
相关产品推荐
相关产品推荐

