Wpf 在图像和 mvvm 控件之间绘制对角线

Wpf drawing diagonal lines between image and mvvm control

在我的示例 wpf 应用程序中,我有一张房子的图片,我在 xaml 中使用椭圆在上面绘制了 4 个湿度传感器。为了在正确的位置绘制传感器,我使用了网格列和行。为了显示传感器值,我创建了一个 HumidityView,它绘制了一个矩形和一个包含实际测量的湿度值的停靠面板。

<Window x:Class="WpfHouseExample.Views.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfHouseExample.Views"
        mc:Ignorable="d"
        Background="Transparent"
        Title="MainView" Height="450" Width="300">

    <Grid ShowGridLines="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <ContentControl Grid.Row="2" Grid.Column="0" Grid.RowSpan="2" x:Name="Humidity1" Margin="0,0,0,2"  HorizontalAlignment="Right"/>
        <ContentControl Grid.Row="6" Grid.Column="0" Grid.RowSpan="2" x:Name="Humidity2" Margin="0,0,0,2"/>
        <ContentControl Grid.Row="2" Grid.Column="5" Grid.RowSpan="2" x:Name="Humidity3" Margin="0,0,0,2"/>
        <ContentControl Grid.Row="6" Grid.Column="5" Grid.RowSpan="2" x:Name="Humidity4" Margin="0,0,0,2"/>
        <Image Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="11" Source="pack://application:,,,/Images/House.png" Margin="20"/>
        <Ellipse Width="20" Height="20" Fill="LightGreen" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="3" Grid.RowSpan="2"/>
        <Ellipse Width="20" Height="20" Fill="LightGreen" Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="3" Grid.RowSpan="2"/>
        <Ellipse Width="20" Height="20" Fill="LightGreen" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="5" Grid.RowSpan="2"/>
        <Ellipse Width="20" Height="20" Fill="LightGreen" Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="5" Grid.RowSpan="2"/>
        <Line Stroke="LightGreen" StrokeThickness="2" Grid.Column="1" Grid.Row="3" Stretch="Fill" X2="1" Y2="1"/>
        <Line Stroke="LightGreen" StrokeThickness="2" Grid.Column="1" Grid.Row="6" Stretch="Fill" X2="1" Y1="1"/>
        <Line Stroke="LightGreen" StrokeThickness="2" Grid.Column="4" Grid.Row="3" Stretch="Fill" X2="1" Y1="1"/>
        <Line Stroke="LightGreen" StrokeThickness="2" Grid.Column="4" Grid.Row="6" Stretch="Fill" X2="1" Y2="1"/>
    </Grid>
</Window>

我的问题是关于从传感器到视图控件的画线。现在我想出了使用网格并在网格中绘制水平线。我真正想做的是从传感器绘制对角线以查看控件。
我找到了图表解决方案,但实现仅使用 canvas,它不支持像网格那样定位控件。 执行此操作的最佳方法是什么?

[编辑 => 有问题的代码更新为在网格中绘制对角线的选项]

您可以创建自定义控件,以便在位于同一父元素内的任意两个控件之间画一条线。

自定义控件会将公共父元素和要连接的两个元素作为参数,然后获取它们的位置和大小,以便为它们之间的直线计算正确的起点和终点。

在我的示例代码中,我从元素的中间画线,但是给定元素矩形,您可以实现任何其他逻辑来确定所需的线端点。

请注意,该示例只是一个小演示,可能既不高效也不完全可用。

自定义控件代码:

/// <summary>
/// Custom Line control to draw a line between two other controls
/// </summary>
public class LineConnectorControl : Control
{
    static LineConnectorControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(LineConnectorControl), new FrameworkPropertyMetadata(typeof(LineConnectorControl)));
    }

    #region Target Properties for Visual Line

    public double X1
    {
        get { return (double)GetValue(X1Property); }
        set { SetValue(X1Property, value); }
    }

    // Using a DependencyProperty as the backing store for X1.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty X1Property =
        DependencyProperty.Register("X1", typeof(double), typeof(LineConnectorControl), new PropertyMetadata(0d));



    public double X2
    {
        get { return (double)GetValue(X2Property); }
        set { SetValue(X2Property, value); }
    }

    // Using a DependencyProperty as the backing store for X2.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty X2Property =
        DependencyProperty.Register("X2", typeof(double), typeof(LineConnectorControl), new PropertyMetadata(0d));



    public double Y1
    {
        get { return (double)GetValue(Y1Property); }
        set { SetValue(Y1Property, value); }
    }

    // Using a DependencyProperty as the backing store for Y1.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty Y1Property =
        DependencyProperty.Register("Y1", typeof(double), typeof(LineConnectorControl), new PropertyMetadata(0d));


    public double Y2
    {
        get { return (double)GetValue(Y2Property); }
        set { SetValue(Y2Property, value); }
    }

    // Using a DependencyProperty as the backing store for Y2.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty Y2Property =
        DependencyProperty.Register("Y2", typeof(double), typeof(LineConnectorControl), new PropertyMetadata(0d));

    #endregion

    #region Source Elements needed to compute Visual Line

    // Positions are computed relative to this element
    public FrameworkElement PositionRoot
    {
        get { return (FrameworkElement)GetValue(PositionRootProperty); }
        set { SetValue(PositionRootProperty, value); }
    }

    // This is the starting point of the line
    public FrameworkElement ConnectedControl1
    {
        get { return (FrameworkElement)GetValue(ConnectedControl1Property); }
        set { SetValue(ConnectedControl1Property, value); }
    }

    // This is the ending point of the line
    public FrameworkElement ConnectedControl2
    {
        get { return (FrameworkElement)GetValue(ConnectedControl2Property); }
        set { SetValue(ConnectedControl2Property, value); }
    }

    // Using a DependencyProperty as the backing store for PositionRoot.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty PositionRootProperty =
        DependencyProperty.Register("PositionRoot", typeof(FrameworkElement), typeof(LineConnectorControl), new FrameworkPropertyMetadata(new PropertyChangedCallback(ElementChanged)));

    // Using a DependencyProperty as the backing store for ConnectedControl1.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ConnectedControl1Property =
        DependencyProperty.Register("ConnectedControl1", typeof(FrameworkElement), typeof(LineConnectorControl), new FrameworkPropertyMetadata(new PropertyChangedCallback(ElementChanged)));

    // Using a DependencyProperty as the backing store for ConnectedControl2.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ConnectedControl2Property =
        DependencyProperty.Register("ConnectedControl2", typeof(FrameworkElement), typeof(LineConnectorControl), new FrameworkPropertyMetadata(new PropertyChangedCallback(ElementChanged)));

    #endregion

    #region Update logic to compute line coordinates based on Source Elements

    private static void ElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var self = (LineConnectorControl)d;
        self.UpdatePositions();
        var fr = (FrameworkElement)e.NewValue;
        fr.SizeChanged += self.ElementSizeChanged;
    }

    private void ElementSizeChanged(object sender, SizeChangedEventArgs e)
    {
        UpdatePositions();
    }

    private void UpdatePositions()
    {
        if (PositionRoot != null && ConnectedControl1 != null && ConnectedControl2 != null)
        {
            Rect rect1 = GetRootedRect(ConnectedControl1);
            Rect rect2 = GetRootedRect(ConnectedControl2);

            X1 = rect1.Location.X + (rect1.Width / 2);
            Y1 = rect1.Location.Y + (rect1.Height / 2);
            X2 = rect2.Location.X + (rect2.Width / 2);
            Y2 = rect2.Location.Y + (rect2.Height / 2);
        }
    }

    private Rect GetRootedRect(FrameworkElement childControl)
    {
        var rootRelativePosition = childControl.TransformToAncestor(PositionRoot).Transform(new Point(0, 0));
        return new Rect(rootRelativePosition, new Size(childControl.ActualWidth, childControl.ActualHeight));
    }

    #endregion
}

Generic.xaml

中的自定义控件视觉样式
<Style TargetType="{x:Type local:LineConnectorControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:LineConnectorControl}">
                <Line X1="{TemplateBinding X1}" X2="{TemplateBinding X2}" Y1="{TemplateBinding Y1}" Y2="{TemplateBinding Y2}" Stroke="Red" StrokeThickness="2"></Line>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

使用示例

<Grid Name="parentGrid">

    <Grid Name="myGrid" ShowGridLines="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" MinWidth="50"/>
            <ColumnDefinition Width="Auto" MinWidth="50"/>
            <ColumnDefinition Width="Auto" MinWidth="50"/>
            <ColumnDefinition Width="Auto" MinWidth="50"/>
            <ColumnDefinition Width="Auto" MinWidth="50"/>
            <ColumnDefinition Width="Auto" MinWidth="50"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Border x:Name="Humidity1" Grid.Row="0" Grid.Column="4" MinWidth="30" Background="Yellow" HorizontalAlignment="Right"/>
        <Border x:Name="Humidity2" Grid.Row="3" Grid.Column="0" Grid.RowSpan="2" Background="Green"/>
    </Grid>
    <!--connecting line-->
    <local:LineConnectorControl PositionRoot="{Binding ElementName=parentGrid}" ConnectedControl1="{Binding ElementName=Humidity1}" ConnectedControl2="{Binding ElementName=Humidity2}"/>
</Grid>