更改字符串类型的匿名 属性 的值

Change value for anonymous property of type string

我正在开发一个用 WPF 编写的应用程序,代码是用 C# 编写的。 我有一个问号图标,当按下它时假设将内容设置为特定标签。 标签内容绑定到视图模型中的 属性,我们称之为 'NoneLegend'。 我希望 属性 在 5 秒后自行清除,因此我有一个实用程序 class 可以管理它。在 class 里面,我写了一个匿名方法来获取任何类型的 属性。 我的问题是如何将 属性 设置为 string.empty? 该方法如下所示:

public static void EmptyStringAfterXseconds<T>(Expression<Func<T>> property)
        {
            var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
            if (propertyInfo == null)
            {
                throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
            }
            else
            {
                var t = propertyInfo.GetType();
                propertyInfo.SetValue(null, "");
            }
        }

我是这样称呼它的:

NoneLegend = "Bla bla...";
Utils.EmptyStringAfterXseconds(() => NoneLegend);

为了让视图中的绑定知道发生了什么变化,您需要通知它有关该变化的信息。 INotifyProperyChanged 接口就是为此目的而设计的。在下面的 ViewModel 代码中实现了这个接口。

我创建了一个简单的 WPF 程序。这是主窗口:

<Window x:Class="WpfApp1.MainWindow"
        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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel>
            <Label x:Name="Legend" Content="{Binding Legend}"></Label>
        </StackPanel>
    </Grid>
</Window>

以及后面的代码:

using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new ViewModel("Text should disappear");
        }
    }
}

和视图模型:

using System.ComponentModel;
using System.Threading;

namespace WpfApp1
{
    public class ViewModel : INotifyPropertyChanged
    {
        Timer LegendTimer;

        public ViewModel(string legend)
        {
            Legend = legend;
        }

        private void StartLegendTimer()
        {
            LegendTimer = new Timer(MakeDisappear, null, 3000, 3000);
        }

        private void StopLegendTimer()
        {
            LegendTimer.Dispose();
            LegendTimer = null;
        }

        private void MakeDisappear(object state)
        {
            Legend = string.Empty;
            StopLegendTimer();
        }

        private string _Legend;

        /// <summary>
        /// Here's where the magic happens
        /// </summary>
        public string Legend
        {
            get => _Legend;
            set
            {
                // Each time the value is set ...
                _Legend = value;
                // we notify the view that a value has changed
                NotifyPropertyChanged(nameof(Legend));
                // If the value is not null or empty
                if(!string.IsNullOrWhiteSpace(value))
                {
                    // We start the time
                    StartLegendTimer();
                }
            }
        }

        private void NotifyPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        // The view automatically fills the PropertyChanged event
        public event PropertyChangedEventHandler PropertyChanged;
    }
}

这个怎么样。我有点担心 new ResetAfterTime() 电话。不知道实例是否存在足够长的时间。它可能会在计时器触发之前由垃圾收集器收集。不得不考虑一下,但它似乎工作正常。

通知程序class:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

namespace WpfApp1
{
    public class PropertyNotifier : INotifyPropertyChanged
    {
        public void NotifyPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        // The view automatically fills the PropertyChanged event
        public event PropertyChangedEventHandler PropertyChanged;
    }
}

视图模型:

using System.ComponentModel;
using System.Threading;

namespace WpfApp1
{
    public class ViewModel : PropertyNotifier
    {
        public ViewModel(string legend)
        {
            Legend = legend;
        }

        private string _Legend;

        /// <summary>
        /// Here's where the magic happens
        /// </summary>
        public string Legend
        {
            get => _Legend;
            set
            {
                _Legend = value;
                new ResetAfterTime<string>(this, () => Legend, value);
            }
        }
    }
}

还有你的增强例程:

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;

namespace WpfApp1
{
    public class ResetAfterTime<T>
            where T : class
    {
        Timer _timer;
        PropertyInfo _propertyInfo;
        PropertyNotifier _notifier;
        int _due;

        public ResetAfterTime(PropertyNotifier notifier, Expression<Func<T>> property, T value, int due = 3000)
        {
            _notifier = notifier;
            _propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
            _due = due;

            if (_propertyInfo == null)
            {
                throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
            }
            else
            {
                if (value != default)
                {
                    StartTimer();
                }
            }
        }
        private void StartTimer()
        {
            _timer = new Timer(MakeDisappear, null, _due, _due);
        }

        private void StopTimer()
        {
            _timer.Dispose();
            _timer = null;
        }

        private void MakeDisappear(object state)
        {
            SetValue(null);
            StopTimer();
        }

        private void SetValue(object value)
        {
            var t = _propertyInfo.GetType();
            _propertyInfo.SetValue(_notifier, value);
            _notifier.NotifyPropertyChanged(_propertyInfo.Name);
        }
    }
}