我正在使用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中似乎不提供该代码的元素(例如DispatcherPriorityDispatcher),因此,除非存在替代方案,否则我认为这不会起作用。

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

任何指针将不胜感激!

最佳答案

您可以通过两种方式来完成此操作。

  • react 性扩展

  • 可以使用Throttle运算符实现所需的行为。

    Observable
    .FromEventPattern<EventArgs>(MapView, nameof(ViewpointChanged));
    .Throttle(TimeSpan.FromMilliSeconds(300));
    .Subscribe(eventPattern => vm.UpdateMapItems(eventPattern.Sender.VisibleArea.Extent));
    

    使用FromEventPattern时,我们会将事件映射到EventPattern实例,其中包括事件的Sender(源)。

    我通过订阅UIElementPointerMoved事件进行了测试。如果我们继续前进,它将多次触发oj​​it_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类跟踪已处理的最后Throttlesender。如“传递给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");
        }
    }
    

    更新



    我想提到几件事:
  • 您对关注点分离的需求是正确的,但是许多开发人员不清楚这到底意味着什么。 View 模型应该完全不知道谁在听,这是毫无疑问的。但是 View 依赖于 View 模型来获取其数据,因此 View 知道 View 模型是可以的。问题更多地是以松散耦合的方式进行的,即。使用绑定(bind)和契约(Contract),而不是直接访问 View 模型成员。
  • 这就是为什么我不特别喜欢Caliburn的 Action 。使用eventHandler,没有契约(例如ICommand)将 View 语法与 View 模型解耦。当然,有绑定(bind)在起作用,因此您仍然会获得解耦的MVVM层。

  • 简而言之,人们选择ReactiveUI而不是Rx.NET来进行WPF开发是有原因的。
    从(_.xaml.cs)后面的 View 代码中,您可以访问:
  • 备用cal:Message.Attach
  • 一个使所有松散耦合的绑定(bind)系统

  • 当然,还有ViewModel,在您的用例中也很方便。

    最后的想法是,如果您的 View 与 View 模型具有相同的生命周期(即,它们在一起放置),则您可能会比较务实,并通过 View 的ReactiveCommands获取 View 模型。

    关于c# - 消除MVVM模式中的事件/命令,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60230128/

    10-17 02:12