如何模拟传感器
How to simulate a sensor
所以我正在做一个需要我做一些硬件模拟的项目。假设我有一条装满传感器的腰带。就像这个一样。
当 obj
到达传感器时,它应该触发传感器,在这种情况下,传感器的状态变为 False
(否则为 True)。我正在使用 PLC,所以当它是 false
或 true
时,我会向 PLC 写入一些内容。我已经涵盖了这一部分。只需要触发传感器的帮助。
就这样
请注意,右侧第一个传感器的颜色变为灰色,这意味着它检测到一个物体,所以现在传感器为 false
(换句话说,已关闭)。正如 obj
通过传感器 returns 到其默认状态,即 true
为此,我想到了以下几点。
- 当传感器检测到物体靠近时发生碰撞
- 我可以有一个区域来代替直光束,当物体落在这个区域时,传感器就会触发。
- 我可以使用计时器(我试过这个)但它让我觉得我实际上是在作弊。所以这不是很可取。
我正在使用 WPF 和 C#。有人能告诉我一些我可以以此为基础构建的工作示例代码吗?
感谢您的宝贵时间!
更新 #1 根据下面的评论
我有 类 以下的问题。
- 传感器(位置、大小)
- 沿着传送带移动的物体。 (位置,大小)
我正在使用的模拟器应该可以查看行为和视觉表示。就像图表一样。出于测试目的,我现在有一个简单的日食。一旦物体关闭,日食的颜色就会改变。一旦物体经过,它就会变回原样。在计时器中执行此操作。
行进的物体总是呈长方形,只是大小不一。用户可以在 2 种对象之间进行选择。一个和现在的图表一样大,另一个稍微大一点可能会导致更早触发传感器。
我不确定您是否打算遵循 MVVM,但无论哪种情况,您都可以尝试这样做:
对于视觉部分,您可以使用 Canvas
,但任何其他面板都可能有效。 Canvas 将允许您使用 Canvas.Left
和类似的东西,并且您所有的定位可能都基于 System.Windows.Point
值,而不是相对定位,无论如何。
其余的东西可以用 System.Window.Shapes.Shape
子类之一绘制。这两个条可能 Rectangle
,对于光束可能是 Line
,对于传感器可能是 Ellipse
或 Rectangle
。
beam 对象应该 Visibility
绑定到您的 ViewModel 中的某些 属性(或者如果不使用 MVVM,则简单地隐藏代码)。 Stroke
属性 的光束和 Fill
属性 的传感器也应该绑定。
我不确定传感器尺寸会如何影响任何事情,这可能意味着光束可能非常宽?如果确实有影响,请同时绑定 Width
和 Height
。如果传感器尺寸很重要,还记得绑定光束的 StrokeThickness
。
对于对象,您将再次使用 Rectangle
。再次,进行必要的绑定。新绑定将是 Canvas.Left
和 Canvas.Top
,以便您知道位置。
现在我们需要为对象设置动画。由于您通过绑定获得了位置,因此您可以更改源中的位置 属性 - 绑定引擎将为您进行渲染更改。您可以使用计时器来移动对象。
现在我们需要找到碰撞。一种方法是在后面计算它的代码,每次对象位置发生变化时都会这样做。还有另一种方法,因为您使用的是 Canvas。阅读此 SO post。您需要做的就是转换对象和梁的基础 Geometry
实例(通过 myobject.RenderedGeometry.FillContainsWithDetail(beam)
)。
当然还有很多其他方法可以做到这一点。我只是希望我设法提供了一个开始你的项目的方向。
你需要HitTesting.
然后你需要设置一个 DependencyProperty
类似 CollisionDetected
到 True
的东西。
然后将 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。
- 为 Beam 对象创建一个
UserControl
。这需要在 Loading
之后立即启动 Task
。它包含一个用于包含 Panel
. 的 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>
运行 应用程序 没有调试 .
所以我正在做一个需要我做一些硬件模拟的项目。假设我有一条装满传感器的腰带。就像这个一样。
当 obj
到达传感器时,它应该触发传感器,在这种情况下,传感器的状态变为 False
(否则为 True)。我正在使用 PLC,所以当它是 false
或 true
时,我会向 PLC 写入一些内容。我已经涵盖了这一部分。只需要触发传感器的帮助。
就这样
请注意,右侧第一个传感器的颜色变为灰色,这意味着它检测到一个物体,所以现在传感器为 false
(换句话说,已关闭)。正如 obj
通过传感器 returns 到其默认状态,即 true
为此,我想到了以下几点。
- 当传感器检测到物体靠近时发生碰撞
- 我可以有一个区域来代替直光束,当物体落在这个区域时,传感器就会触发。
- 我可以使用计时器(我试过这个)但它让我觉得我实际上是在作弊。所以这不是很可取。
我正在使用 WPF 和 C#。有人能告诉我一些我可以以此为基础构建的工作示例代码吗?
感谢您的宝贵时间!
更新 #1 根据下面的评论
我有 类 以下的问题。
- 传感器(位置、大小)
- 沿着传送带移动的物体。 (位置,大小)
我正在使用的模拟器应该可以查看行为和视觉表示。就像图表一样。出于测试目的,我现在有一个简单的日食。一旦物体关闭,日食的颜色就会改变。一旦物体经过,它就会变回原样。在计时器中执行此操作。
行进的物体总是呈长方形,只是大小不一。用户可以在 2 种对象之间进行选择。一个和现在的图表一样大,另一个稍微大一点可能会导致更早触发传感器。
我不确定您是否打算遵循 MVVM,但无论哪种情况,您都可以尝试这样做:
对于视觉部分,您可以使用 Canvas
,但任何其他面板都可能有效。 Canvas 将允许您使用 Canvas.Left
和类似的东西,并且您所有的定位可能都基于 System.Windows.Point
值,而不是相对定位,无论如何。
其余的东西可以用 System.Window.Shapes.Shape
子类之一绘制。这两个条可能 Rectangle
,对于光束可能是 Line
,对于传感器可能是 Ellipse
或 Rectangle
。
beam 对象应该 Visibility
绑定到您的 ViewModel 中的某些 属性(或者如果不使用 MVVM,则简单地隐藏代码)。 Stroke
属性 的光束和 Fill
属性 的传感器也应该绑定。
我不确定传感器尺寸会如何影响任何事情,这可能意味着光束可能非常宽?如果确实有影响,请同时绑定 Width
和 Height
。如果传感器尺寸很重要,还记得绑定光束的 StrokeThickness
。
对于对象,您将再次使用 Rectangle
。再次,进行必要的绑定。新绑定将是 Canvas.Left
和 Canvas.Top
,以便您知道位置。
现在我们需要为对象设置动画。由于您通过绑定获得了位置,因此您可以更改源中的位置 属性 - 绑定引擎将为您进行渲染更改。您可以使用计时器来移动对象。
现在我们需要找到碰撞。一种方法是在后面计算它的代码,每次对象位置发生变化时都会这样做。还有另一种方法,因为您使用的是 Canvas。阅读此 SO post。您需要做的就是转换对象和梁的基础 Geometry
实例(通过 myobject.RenderedGeometry.FillContainsWithDetail(beam)
)。
当然还有很多其他方法可以做到这一点。我只是希望我设法提供了一个开始你的项目的方向。
你需要HitTesting.
然后你需要设置一个 DependencyProperty
类似 CollisionDetected
到 True
的东西。
然后将 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。
- 为 Beam 对象创建一个
UserControl
。这需要在Loading
之后立即启动Task
。它包含一个用于包含Panel
. 的 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>
运行 应用程序 没有调试 .