我正在使用Caliburn.Micro构建通用Windows应用程序,不幸的是,由于某些硬件限制,我们需要以Windows 10 1607为目标,因此无法实现依赖于.NET Standard/UWP 16299的任何程序包,其中包括ReactiveUI。
在这种特殊情况下,我有一个首先使用 View 模型的方法来生成 map (和其他资源),然后将它们绑定(bind)到XAML View 中的mapview。理想情况下,我想在通过ViewpointChanged
事件移动 map 时触发一个过程。
查看模型
public class ExampleViewModel : Screen
{
public ExampleViewModel()
{
Map = new Map();
}
public Map Map { get; set; }
public BindableCollection<MapItems> MapItems { get; set; }
private UpdateMapItems(Envelope visibleArea)
{
// The visibleArea param will include the current viewpoint of the map view
// This method will effectively generate the appropriate map items based on the current coordinates
}
}
查看
...
<MapView x:Name="MapView" Map="{Binding Map}" cal:Message.Attach="[Event ViewpointChanged] = [Action UpdateMapItems(MapView.VisibleArea.Extent)]" />
...
现在这在技术上可行,但存在一个主要缺陷,即 map 的每个运动都会多次触发ViewpointChanged事件(例如,效果类似于OnMouseMove)。
理想情况下,我希望能够限制/反跳此事件,以便仅在 View 未移动300ms时处理 map 项。
我发现了一篇涉及实现
DispatcherTimer
的文章,但是UWP中似乎不提供该代码的元素(例如DispatcherPriority
和Dispatcher
),因此,除非存在替代方案,否则我认为这不会起作用。我看过System.Reactive,但是对于我要实现的目标来说,这似乎异常复杂。
任何指针将不胜感激!
最佳答案
您可以通过两种方式来完成此操作。
可以使用
Throttle
运算符实现所需的行为。Observable
.FromEventPattern<EventArgs>(MapView, nameof(ViewpointChanged));
.Throttle(TimeSpan.FromMilliSeconds(300));
.Subscribe(eventPattern => vm.UpdateMapItems(eventPattern.Sender.VisibleArea.Extent));
使用
FromEventPattern
时,我们会将事件映射到EventPattern实例,其中包括事件的Sender
(源)。我通过订阅
UIElement
的PointerMoved
事件进行了测试。如果我们继续前进,它将多次触发ojit_code。但是,对于HandleEvent
,事件处理程序仅执行一次。这是我们停止移动后经过间隔的时间。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
类跟踪已处理的最后Throttle
和sender
。如“传递给args
进行处理”中所述进行处理。只有当计时器过去且没有其他事件发生时,才会实际执行Throttle
(作为构造函数参数传递)。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");
}
}
更新
我想提到几件事:
eventHandler
,没有契约(例如ICommand)将 View 语法与 View 模型解耦。当然,有绑定(bind)在起作用,因此您仍然会获得解耦的MVVM层。 简而言之,人们选择ReactiveUI而不是Rx.NET来进行WPF开发是有原因的。
从(_.xaml.cs)后面的 View 代码中,您可以访问:
cal:Message.Attach
当然,还有
ViewModel
,在您的用例中也很方便。最后的想法是,如果您的 View 与 View 模型具有相同的生命周期(即,它们在一起放置),则您可能会比较务实,并通过 View 的
ReactiveCommands
获取 View 模型。关于c# - 消除MVVM模式中的事件/命令,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60230128/