Это можно сделать несколькими способами.
- Реактивные расширения
Требуемое поведение может быть достигнуто с помощью оператора Throttle
.
Observable
.FromEventPattern<EventArgs>(MapView, nameof(ViewpointChanged));
.Throttle(TimeSpan.FromMilliSeconds(300));
.Subscribe(eventPattern => vm.UpdateMapItems(eventPattern.Sender.VisibleArea.Extent));
При использовании FromEventPattern
мы сопоставляем события с экземплярами EventPattern , который включает Sender
(источник) события.
Я протестировал, подписавшись на событие UIElement
PointerMoved
. Что вызывает HandleEvent
несколько раз, если мы продолжаем двигаться. Однако при Throttle
обработчик событий выполняется только один раз. Это когда интервал прошел после , мы прекращаем движение.
MainPage.xaml
<Page
x:Class="..."
...
>
<Grid>
<Button x:Name="MyUIElement" Content="Throttle Surface"
Height="250" Width="250" HorizontalAlignment="Center"/>
</Grid>
</Page>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
Observable
.FromEventPattern<PointerRoutedEventArgs>(MyUIElement, nameof(UIElement.PointerMoved))
.Throttle(TimeSpan.FromMilliseconds(300))
.Subscribe(eventPattern => HandleEvent(eventPattern.Sender, eventPattern.EventArgs));
}
private void HandleEvent(object source, PointerRoutedEventArgs args)
{
Debug.WriteLine("Pointer Moved");
}
}
Что-то нестандартное
Наш пользовательский класс Throttle
отслеживает последние sender
и args
, которые были обработаны. Обрабатывается как в «передано на Throttle
для обработки». Только когда истекает таймер, и никаких других событий не происходит, eventHandler
(передается в качестве аргумента конструктора) фактически выполняется.
public class Throttle<TEventArgs>
{
private readonly DispatcherTimer _timer;
private object _lastSender;
private TEventArgs _lastEventArgs;
public Throttle(EventHandler<TEventArgs> eventHandler, TimeSpan interval)
{
_timer = new DispatcherTimer
{
Interval = interval
};
_timer.Tick += (s, e) =>
{
_timer.Stop();
eventHandler(_lastSender, _lastEventArgs);
};
}
public void ProcessEvent(object sender, TEventArgs args)
{
_timer.Stop();
_timer.Start();
_lastSender = sender;
_lastEventArgs = args;
}
}
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
private readonly Throttle<PointerRoutedEventArgs> _throttle;
public MainPage()
{
this.InitializeComponent();
var interval = TimeSpan.FromMilliseconds(300);
_throttle = new Throttle<PointerRoutedEventArgs>(HandleEvent, interval);
MyUIElement.PointerMoved += (sender, e) => _throttle.ProcessEvent(sender, e);
}
private void HandleEvent(object sender, PointerRoutedEventArgs e)
{
Debug.WriteLine("Pointer Moved");
}
}
Обновление
Я изо всех сил пытаюсь понять, как все сочетается в среде MVVM. Логика c, которая должна быть вызвана событием, содержится внутри ViewModel, но View и ViewModel должны быть полностью отделены.
Есть пара вещей, которые я хотел бы упомянуть:
- Вы правы насчет необходимости разделения интересов, но многим разработчикам неясно, что именно это влечет за собой. Модель представления должна полностью не знать, кто слушает, в этом нет никаких сомнений. Но представление зависит от модели представления, чтобы получить свои данные, так что представление может знать о модели представления. Проблема, скорее, заключается в том, чтобы сделать это в слабосвязанной форме, ie. использование привязок и контрактов вместо прямого доступа к членам модели представления.
- Вот почему мне не особенно нравятся действия Калибурна. С
cal:Message.Attach
нет контракта (например, ICommand) для отделения синтаксиса представления от модели представления. Конечно, в игре есть привязки, поэтому вы по-прежнему получаете разделенные слои MVVM.
Короче говоря, есть причина, по которой люди выбирают ReactiveUI вместо Rx. NET для разработки WPF. Из кода представления (_.xaml.cs) он дает вам доступ к:
- Поддержка
ViewModel
- Система привязки, чтобы все это было свободно связано
И, конечно, ReactiveCommands
, что также пригодится в вашем случае использования.
Заключительные мысли, если ваш вид имеет тот же срок службы, что и модель вашего вида (ie они располагаются вместе), вы можете прагматически c об этом и получить модель представления через DataContext
вашего представления.