我正在使用Caliburn.Micro构建通用Windows应用程序,不幸的是,由于某些硬件限制,我们需要以Windows 10 1607为目标,因此无法实现依赖于.NET Standard / UWP 16299的任何程序包,其中包括ReactiveUI。
在这种特定情况下,我有一个首先使用视图模型的方法来生成地图(和其他资源),然后将它们绑定到XAML视图中的mapview。理想情况下,我想通过ViewpointChanged
事件移动地图时触发一个过程。
查看模型
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)]" />
...
现在,此在技术上有效,但主要缺陷在于,地图的每次移动都会触发ViewpointChanged事件多次(例如,与OnMouseMove的效果类似)。
理想情况下,我希望能够限制/反跳此事件,以便仅在视图未移动300ms时处理地图项。
我找到了一篇涉及实现DispatcherTimer
的文章,但是该代码中的元素,例如DispatcherPriority
和Dispatcher
在UWP中似乎不可用,因此除非存在替代方法,否则我认为这行不通。
我看过System.Reactive,但这对于我想要实现的目标来说似乎异常复杂。
任何指针将不胜感激!
答案 0 :(得分:1)
您可以通过以下两种方式进行操作。
可以使用Throttle
运算符来实现所需的行为。
Observable
.FromEventPattern<EventArgs>(MapView, nameof(ViewpointChanged));
.Throttle(TimeSpan.FromMilliSeconds(300));
.Subscribe(eventPattern => vm.UpdateMapItems(eventPattern.Sender.VisibleArea.Extent));
使用FromEventPattern
时,我们会将事件映射到EventPattern的实例,其中包括事件的Sender
(源)。
我通过订阅UIElement
的{{1}}事件进行了测试。如果我们继续前进,它将多次触发PointerMoved
。但是,对于HandleEvent
,事件处理程序仅执行一次。这是间隔时间过了 之后,我们才停止移动。
MainPage.xaml
Throttle
MainPage.xaml.cs
<Page
x:Class="..."
...
>
<Grid>
<Button x:Name="MyUIElement" Content="Throttle Surface"
Height="250" Width="250" HorizontalAlignment="Center"/>
</Grid>
</Page>
我们的自定义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
MainPage.xaml.cs
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;
}
}
更新
我正在努力弄清在MVVM环境中所有组件如何组合在一起。该事件需要触发的逻辑包含在ViewModel中,但是View和ViewModel应该完全分开。
我想说几件事:
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");
}
}
时,没有合同(例如ICommand)将视图语法与视图模型分离。当然,有绑定在起作用,所以您仍然会获得解耦的MVVM层。长话短说,人们选择ReactiveUI而不是Rx.NET来进行WPF开发是有原因的。 从视图(_.xaml.cs)后面的代码,您可以访问:
cal:Message.Attach
当然还有ViewModel
,这在您的用例中也会派上用场。
最后的想法,如果您的视图与视图模型具有相同的生命周期(即它们在一起放置),那么您可能会很实用,并通过视图的ReactiveCommands
获取视图模型。