如何模拟传感器

How to simulate a sensor

所以我正在做一个需要我做一些硬件模拟的项目。假设我有一条装满传感器的腰带。就像这个一样。

obj 到达传感器时,它应该触发传感器,在这种情况下,传感器的状态变为 False(否则为 True)。我正在使用 PLC,所以当它是 falsetrue 时,我会向 PLC 写入一些内容。我已经涵盖了这一部分。只需要触发传感器的帮助。

就这样

请注意,右侧第一个传感器的颜色变为灰色,这意味着它检测到一个物体,所以现在传感器为 false(换句话说,已关闭)。正如 obj 通过传感器 returns 到其默认状态,即 true

为此,我想到了以下几点。

我正在使用 WPF 和 C#。有人能告诉我一些我可以以此为基础构建的工作示例代码吗?

感谢您的宝贵时间!

更新 #1 根据下面的评论

我有 类 以下的问题。

我正在使用的模拟器应该可以查看行为和视觉表示。就像图表一样。出于测试目的,我现在有一个简单的日食。一旦物体关闭,日食的颜色就会改变。一旦物体经过,它就会变回原样。在计时器中执行此操作。

行进的物体总是呈长方形,只是大小不一。用户可以在 2 种对象之间进行选择。一个和现在的图表一样大,另一个稍微大一点可能会导致更早触发传感器。

我不确定您是否打算遵循 MVVM,但无论哪种情况,您都可以尝试这样做:

对于视觉部分,您可以使用 Canvas,但任何其他面板都可能有效。 Canvas 将允许您使用 Canvas.Left 和类似的东西,并且您所有的定位可能都基于 System.Windows.Point 值,而不是相对定位,无论如何。

其余的东西可以用 System.Window.Shapes.Shape 子类之一绘制。这两个条可能 Rectangle,对于光束可能是 Line,对于传感器可能是 EllipseRectangle

beam 对象应该 Visibility 绑定到您的 ViewModel 中的某些 属性(或者如果不使用 MVVM,则简单地隐藏代码)。 Stroke 属性 的光束和 Fill 属性 的传感器也应该绑定。

我不确定传感器尺寸会如何影响任何事情,这可能意味着光束可能非常宽?如果确实有影响,请同时绑定 WidthHeight。如果传感器尺寸很重要,还记得绑定光束的 StrokeThickness

对于对象,您将再次使用 Rectangle。再次,进行必要的绑定。新绑定将是 Canvas.LeftCanvas.Top,以便您知道位置。

现在我们需要为对象设置动画。由于您通过绑定获得了位置,因此您可以更改源中的位置 属性 - 绑定引擎将为您进行渲染更改。您可以使用计时器来移动对象。

现在我们需要找到碰撞。一种方法是在后面计算它的代码,每次对象位置发生变化时都会这样做。还有另一种方法,因为您使用的是 Canvas。阅读此 SO post。您需要做的就是转换对象和梁的基础 Geometry 实例(通过 myobject.RenderedGeometry.FillContainsWithDetail(beam))。

当然还有很多其他方法可以做到这一点。我只是希望我设法提供了一个开始你的项目的方向。

你需要HitTesting.

然后你需要设置一个 DependencyProperty 类似 CollisionDetectedTrue 的东西。

然后将 DataTrigger 应用到您的 Beam xaml 以更改其 Style/BackgroundColor

   <uc:BeamControl>
        <uc:BeamControl.Style>
            <Style TargetType="BeamControl">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding CollisionDetected}" Value="True">
                        <Setter Property="Background" Value="Red" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </uc:BeamControl.Style>
    </uc:BeamControl>

************************ 新增代码****************** ******

在这个可行的解决方案中,我使用了附件 属性。我让一切都变得简单,以专注于核心问题。

Window2.xaml

<Window x:Class="WpfAnimation.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfAnimation"
        Title="Window2" Height="300" Width="900">
    <Canvas>
        <Path local:Window2.MovingObjectPos="{Binding (Canvas.Left), ElementName=ElpObj}"  Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Canvas.Left="100" Canvas.Top="50" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z">

        </Path>
        <Path local:Window2.MovingObjectPos="{Binding (Canvas.Left), ElementName=ElpObj}"  Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Canvas.Left="200" Canvas.Top="50" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z">

        </Path>
        <Path local:Window2.MovingObjectPos="{Binding (Canvas.Left), ElementName=ElpObj}"  Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Canvas.Left="300" Canvas.Top="50" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z">

        </Path>
        <Path local:Window2.MovingObjectPos="{Binding (Canvas.Left), ElementName=ElpObj}"  Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Canvas.Left="400" Canvas.Top="50" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z">

        </Path>
        <Ellipse x:Name="ElpObj" Fill="Pink" Stroke="Black" HorizontalAlignment="Right" VerticalAlignment="Top" Width="57" Height="51" Canvas.Left="701" Canvas.Top="115">
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" To="50" By="-2.0"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>
</Window>

Window2.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WpfAnimation
{
    /// <summary>
    /// Interaction logic for Window2.xaml
    /// </summary>
    public partial class Window2 : Window
    {
        public Window2()
        {
            InitializeComponent();
        }


        public static double GetMovingObjectPos(DependencyObject obj)
        {
            return (double)obj.GetValue(MovingObjectPosProperty);
        }

        public static void SetMovingObjectPos(DependencyObject obj, double value)
        {
            obj.SetValue(MovingObjectPosProperty, value);
        }

        // Using a DependencyProperty as the backing store for MovingObject.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MovingObjectPosProperty =
            DependencyProperty.RegisterAttached("MovingObjectPos", typeof(double), typeof(Window2), new PropertyMetadata(0.0, new PropertyChangedCallback(MovingObjectPosChanged)));


        private static void MovingObjectPosChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            double leftOfMovingObject = (double) e.NewValue ;
            Path beam = (Path) d;

            System.Diagnostics.Debug.WriteLine("Left = " + e.NewValue.ToString());

            double leftOfBeam = Canvas.GetLeft(beam);
            double widthOfBeam = 20.0;

            if (leftOfMovingObject > leftOfBeam && leftOfMovingObject < leftOfBeam + widthOfBeam)
            {
                System.Diagnostics.Debug.WriteLine("Hit >>>>> = " + e.NewValue.ToString());

                beam.Fill = Brushes.Gray;
            }
        }


    }
}

我使用 Task 改进了我的旧答案,并考虑了无限移动物体。

它使用实际 HitTesting

  1. 为 Beam 对象创建一个 UserControl。这需要在 Loading 之后立即启动 Task。它包含一个用于包含 Panel.
  2. 的 DP

BeamControl.xaml

<UserControl x:Class="WpfAnimation.BeamControl" ... 
             Height="300" Width="39">
        <Path Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z"/>
</UserControl>

BeamControl.xaml.cs

using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WpfAnimation
{
    /// <summary>
    /// Interaction logic for BeamControl.xaml
    /// </summary>
    public partial class BeamControl : UserControl
    {

        Path _beamPath;

        public Panel Sensor
        {
            get { return (Panel)GetValue(SensorProperty); }
            set { SetValue(SensorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Sensor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SensorProperty =
            DependencyProperty.Register("Sensor", typeof(Panel), typeof(BeamControl), new PropertyMetadata(null));

        private  double left;
        private double top;

        public BeamControl()
        {
            InitializeComponent();

            this.Loaded += BeamControl_Loaded;             
        }

        void BeamControl_Loaded(object sender, RoutedEventArgs e)
        {
            _beamPath = this.Content as Path;

            left = Canvas.GetLeft(this);
            top = Canvas.GetTop(this);

            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    _doDetection();
                }

            });
        }

        void _doDetection()
        {
            // 200 is the height of the beam, you can change it
            for (double i = top; i < 200; i = i + 1)
            {
                System.Diagnostics.Debug.WriteLine(i.ToString());
                Point pt = new Point(left + 20.0, i);

                this.Dispatcher.Invoke(() =>
                {
                    VisualTreeHelper.HitTest(Sensor,
                        new HitTestFilterCallback((o) =>
                        {
                            if (o is Ellipse)
                            {
                                _beamPath.Fill = Brushes.DarkKhaki;
                                System.Diagnostics.Debug.WriteLine("Detected ! " + o.GetType().ToString());
                                System.Diagnostics.Debug.WriteLine(pt.ToString());
                                return HitTestFilterBehavior.Stop;
                            }
                            else
                                System.Diagnostics.Debug.WriteLine(o.GetType().ToString());

                            return HitTestFilterBehavior.Continue;
                        }),
                        new HitTestResultCallback((result) => { return HitTestResultBehavior.Continue; }),
                        new PointHitTestParameters(pt));
                });
            }
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfAnimation.MainWindow"
        ...
        Title="Window5" Height="303.383" Width="622.556">
    <Canvas>
        <local:BeamControl Canvas.Left="200" Canvas.Top="50" Sensor="{Binding RelativeSource={RelativeSource AncestorType=Canvas, Mode=FindAncestor}}"/>

        <local:BeamControl Canvas.Left="300" Canvas.Top="50" Sensor="{Binding RelativeSource={RelativeSource AncestorType=Canvas, Mode=FindAncestor}}"/>

        <Ellipse x:Name="ElpObj" Fill="Pink" Stroke="Black" HorizontalAlignment="Right" VerticalAlignment="Top" Width="57" Height="51" Canvas.Left="548" Canvas.Top="114">
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" To="50" By="-0.5" Duration="0:0:20"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>
</Window>

运行 应用程序 没有调试 .