消除MVVM模式中的事件/命令

时间:2020-02-14 16:27:01

标签: c# mvvm uwp caliburn.micro

我正在使用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的文章,但是该代码中的元素,例如DispatcherPriorityDispatcher在UWP中似乎不可用,因此除非存在替代方法,否则我认为这行不通。

我看过System.Reactive,但这对于我想要实现的目标来说似乎异常复杂。

任何指针将不胜感激!

1 个答案:

答案 0 :(得分:1)

您可以通过以下两种方式进行操作。

  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>
  1. 自定义项

我们的自定义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"); } } 类跟踪已处理的最后Throttlesender。按照“传递到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应该完全分开。

我想说几件事:

  • 您对关注点分离的需求是正确的,但是许多开发人员不清楚这到底意味着什么。视图模型应该完全不知道谁在听,这是毫无疑问的。但是视图依赖于视图模型来获取其数据,因此视图知道视图模型是可以的。问题更多地是以松散耦合的方式进行的,即。使用绑定和合同,而不是直接访问视图模型成员。
  • 这就是为什么我不特别喜欢Caliburn的动作。使用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获取视图模型。

相关问题