#c# #anonymous-types
#c# #анонимные типы
Вопрос:
Я работаю над приложением, написанным на WPF, код написан на C #. У меня есть значок вопросительного знака, который при нажатии предполагает установку содержимого на определенную метку. Содержимое метки привязано к свойству в модели представления, давайте назовем его ‘NoneLegend’. Я хочу, чтобы это свойство самоочищалось через 5 секунд, поэтому у меня есть служебный класс, который должен управлять этим. Внутри этого класса я написал анонимный метод, который получает любой тип свойства. Мой вопрос в том, как мне установить для этого свойства значение 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);
Комментарии:
1. Я думаю, вам следует реализовать INotifyPropertyChanged и вызвать PropertyChanged(nameof(NoneLegend)), чтобы изменения были отражены в поле.
2. Боюсь, что INotifyPropertyChanged здесь не является частью решения. Цель состоит в том, чтобы изменить значение через 5 секунд из универсального метода.
3. Я сделал для вас пример. Скоро появится сообщение.
Ответ №1:
Как насчет этого. Я немного беспокоюсь о new ResetAfterTime()
вызове. Не знаю, достаточно ли долго существует экземпляр. Оно может быть собрано сборщиком мусора до срабатывания таймера. Об этом нужно было бы подумать, но, похоже, все работает нормально.
Класс-уведомитель:
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;
}
}
Viewmodel:
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);
}
}
}
Комментарии:
1. Отличное решение, Пол, большое спасибо! Поскольку я использую свой собственный ObservableObject, который обрабатывает все мои события NotifyPropertyChanged, мне пришлось немного настроить ваш код, чтобы он соответствовал моей структуре, но после этого все работает как по маслу! Это чистый универсальный код, и я могу использовать его во всех легендах моего приложения. Большое вам спасибо за то, что вложили средства в такие прекрасные усилия !!! 🙂
2. Нет проблем. Рад, что смог помочь
Ответ №2:
Чтобы привязка в представлении знала, что изменилось, вам нужно уведомить ее об этом изменении. Интерфейс 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");
}
}
}
И ViewModel:
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;
}
}
Комментарии:
1. Дорогой Пол, большое тебе спасибо за прекрасный код и усилия. Я должен сказать, что я хорошо знаком с wpf и интерфейсом INotifyPropertyChanged и уже думал об этом варианте. Проблема в том, как мы можем обрабатывать несколько свойств? Изображение у вас есть 10 условных обозначений для 10 меток в вашем xaml, каждая из которых размещена в другом месте и нацелена на что-то другое. Вот почему я в первую очередь подумал об анонимном типе. У вас есть какие-либо идеи, как это решить? Ваш таймер обращается непосредственно к определенному свойству, и мне нужно, чтобы оно было чисто универсальным.
2. Кстати, моя ViewModel уже содержит INotifyPropertyChanged, я не вижу, как можно работать без него 🙂