【问题标题】:WPF place arbitrary content around data-bound elements on a canvasWPF 在画布上围绕数据绑定元素放置任意内容
【发布时间】:2021-03-04 17:13:50
【问题描述】:

我们有一个(动态)列表,其中包含我们想要放置在画布上的事物(几种类型,来自相同的基础或接口)。事物指定了它们的位置。这些东西包含一些必须显示在画布上的确切位置的图形。例如,地图(作为画布背景)和具有特定符号的城市列表。事物的大小可以在运行时改变。 但是我们也想围绕这些东西添加一些其他的内容,不是固定大小的内容。它必须相对于事物定位,但不影响它们的位置。例如,添加边框以指示选择。或者城市的名称,或者它的人口数量。

我已经使用附加属性(一种行为)解决了这个问题,请参阅下面我自己的答案。

有人知道更好的方法吗?

【问题讨论】:

    标签: c# wpf canvas data-binding


    【解决方案1】:

    我解决这个问题的方法是将属性(数据绑定)附加到关键图形元素。这些属性背后的代码调整了ItemContainerCanvas.LeftCanvas.Top 属性。从另一端(绑定容器属性的样式)执行此操作不起作用,或者至少我想不出一种优雅的方式来引用容器样式中的数据模板的成员。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    
    namespace WpfApp2
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                DataContext = new List<Thing>
                {
                    new Marker() { Position = new Point(10, 10) },
                    new Marker() { Position = new Point(250, 50) },
                    new Marker() { Position = new Point(50, 250) },
                    new Marker() { Position = new Point(250, 250) },
                    new Marker() { Position = new Point(150, 150) },
                    new RoundThing() { Name = "R1", Radius = 10, Position = new Point(10, 10) },
                    new RoundThing() { Name = "R2", Radius = 20, Position = new Point(250, 50) },
                    new RoundThing() { Name = "R3", Radius = 30, Position = new Point(50, 250) },
                    new RoundThing() { Name = "R4", Radius = 40, Position = new Point(250, 250) },
                    new ComplexThing() { Name = "CX", Position = new Point(150, 150)},
                };
            }
    
            private void Canvas_PreviewMouseMove(object sender, MouseEventArgs e)
            {
                mousePos.Text = Mouse.GetPosition(this).ToString();
            }
        }
    
        public abstract class Thing : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
            public void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string property = null)
            {
    #if DEBUG
                var pi = GetType().GetProperty(property);
                if (pi == null)
                    throw new ArgumentException($"Property {property} not found on {this}");
    #endif
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
            }
    
    
            public string Name
            {
                get => name;
                set
                {
                    if (name == value)
                        return;
                    name = value;
                    RaisePropertyChanged();
                }
            }
            private string name;
    
            public Point Position
            {
                get => point;
                set
                {
                    if (point == value)
                        return;
                    point = value;
                    RaisePropertyChanged();
                }
            }
            private Point point;
    
    
            public Point Offset
            {
                get => offset;
                set
                {
                    if (offset == value)
                        return;
                    offset = value;
                    RaisePropertyChanged();
                }
            }
            Point offset;
    
    
    
            public Geometry Geometry
            {
                get => geometry ??= CreateGeometry();
                set
                {
                    if (geometry == value)
                        return;
                    geometry = value;
                    RaisePropertyChanged();
                }
            }
            private Geometry geometry;
    
            protected abstract Geometry CreateGeometry();
        }
    
        public class Marker : Thing
        {
            protected override Geometry CreateGeometry()
            {
                return g;
            }
    
            readonly static Geometry g;
    
            static Marker()
            {
                g = new EllipseGeometry();
                if (g.CanFreeze)
                    g.Freeze();
            }
        }
    
        public class RoundThing : Thing
        {
            public double Radius
            {
                get => radius;
                set
                {
                    if (radius == value)
                        return;
                    radius = value;
                    RaisePropertyChanged();
                    Geometry = null;
                }
            }
            protected double radius;
    
            protected override Geometry CreateGeometry()
            {
                Offset = new Point(radius, radius);
                var g = new EllipseGeometry(new Point(radius, radius), radius, radius);
                if (g.CanFreeze)
                    g.Freeze();
                return g;
            }
        }
    
        public class ComplexThing : Thing
        {
            protected override Geometry CreateGeometry()
            {
                Offset = new Point(25, 10);
                var grp = new GeometryGroup();
                grp.Children.Add(new EllipseGeometry(new Point(10, 10), 10, 10));
                grp.Children.Add(new EllipseGeometry(new Point(40, 10), 10, 10));
                grp.Children.Add(new EllipseGeometry(new Point(25, 30), 10, 10));
                if (grp.CanFreeze)
                    grp.Freeze();
                return grp;
            }
        }
    }
    
    <Window x:Class="WpfApp2.MainWindow"
            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:WpfApp2"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Window.Resources>
            <DataTemplate DataType="{x:Type local:Thing}">
                <Border BorderThickness="1" BorderBrush="Red">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <Path Grid.Column="1" Data="{Binding Geometry}" Stroke="Black" StrokeThickness="2"
                              local:PositionContainer.Position="{Binding Position}"
                              local:PositionContainer.Offset="{Binding Offset}"
                              local:PositionContainer.Container="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContentPresenter}}}"/>
                        <TextBlock Text="some text"/>
                        <TextBlock Grid.Row="1" Grid.ColumnSpan="2" Text="some rather long text"/>
                        <TextBlock Grid.Column="2" Text="{Binding Name}" FontWeight="Bold"/>
                    </Grid>
                </Border>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:RoundThing}">
                <Border BorderThickness="1" BorderBrush="Red">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <Path Grid.Column="1" Data="{Binding Geometry}" Stroke="Black" StrokeThickness="2"
                              local:PositionContainer.Position="{Binding Position}"
                              local:PositionContainer.Offset="{Binding Offset}"
                              local:PositionContainer.Container="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContentPresenter}}}"/>
                        <TextBlock Text="some text"/>
                        <TextBlock Grid.Row="1" Grid.ColumnSpan="2" Text="some rather long text"/>
                        <StackPanel Grid.Column="2">
                            <TextBlock Text="{Binding Name}" FontWeight="Bold"/>
                            <TextBlock Text="{Binding Radius, StringFormat=f2}"/>
                        </StackPanel>
                        <Slider Grid.Row="2" Grid.ColumnSpan="3" Minimum="0" Maximum="100" Value="{Binding Radius}"/>
                    </Grid>
                </Border>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:Marker}">
                <Ellipse Width="6" Height="6" Margin="-3" Fill="DarkViolet" Canvas.Left="{Binding Position.X}" Canvas.Top="{Binding Position.Y}"/>
            </DataTemplate>
        </Window.Resources>
    
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <ItemsControl ItemsSource="{Binding}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas IsItemsHost="True" Background="Transparent" PreviewMouseMove="Canvas_PreviewMouseMove"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemContainerStyle>
                    <Style TargetType="ContentPresenter"> <!--so that the markers are placed at their positions-->
                        <Setter Property="Canvas.Left" Value="{Binding Position.X}"/>
                        <Setter Property="Canvas.Top" Value="{Binding Position.Y}"/>
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>
            <TextBlock x:Name="mousePos" Grid.Row="1"/>
        </Grid>
    </Window>
    
    using System.Windows;
    using System.Windows.Controls;
    
    namespace WpfApp2
    {
        public class PositionContainer
        {
            public static readonly DependencyProperty PositionProperty =
                 DependencyProperty.RegisterAttached("Position", typeof(Point), typeof(PositionContainer),
                 new PropertyMetadata(default(Point), OnPositionChanged));
    
            public static void SetPosition(FrameworkElement element, Point value)
            {
                element.SetValue(PositionProperty, value);
            }
    
            public static Point GetPosition(FrameworkElement element)
            {
                return (Point)element.GetValue(PositionProperty);
            }
    
            public static readonly DependencyProperty OffsetProperty =
                 DependencyProperty.RegisterAttached("Offset", typeof(Point), typeof(PositionContainer),
                 new PropertyMetadata(default(Point), OnOffsetChanged));
    
            public static void SetOffset(FrameworkElement element, Point value)
            {
                element.SetValue(OffsetProperty, value);
            }
    
            public static Point GetOffset(FrameworkElement element)
            {
                return (Point)element.GetValue(OffsetProperty);
            }
    
            public static readonly DependencyProperty ContainerProperty =
                 DependencyProperty.RegisterAttached("Container", typeof(FrameworkElement), typeof(PositionContainer),
                 new PropertyMetadata(default(FrameworkElement), OnContainerChanged));
    
            public static void SetContainer(FrameworkElement element, FrameworkElement value)
            {
                element.SetValue(ContainerProperty, value);
            }
    
            public static FrameworkElement GetContainer(FrameworkElement element)
            {
                return (FrameworkElement)element.GetValue(ContainerProperty);
            }
    
    
            public static readonly DependencyProperty SourceProperty =
                 DependencyProperty.RegisterAttached("Source", typeof(FrameworkElement), typeof(PositionContainer),
                 new PropertyMetadata(default(FrameworkElement)));
    
            public static void SetSource(FrameworkElement element, FrameworkElement value)
            {
                element.SetValue(SourceProperty, value);
            }
    
            public static FrameworkElement GetSource(FrameworkElement element)
            {
                return (FrameworkElement)element.GetValue(SourceProperty);
            }
    
    
            static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                Apply((FrameworkElement)d);
            }
    
            static void OnOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                Apply((FrameworkElement)d);
            }
    
            static void OnContainerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var source = (FrameworkElement)d;
                if (e.OldValue is FrameworkElement oldContainer)
                {
                    oldContainer.SizeChanged -= OnSizeChanged;
                    SetSource(oldContainer, null);
                }
                if (e.NewValue is FrameworkElement newContainer)
                {
                    newContainer.SizeChanged += OnSizeChanged;
                    SetSource(newContainer, source);
                    Apply(source);
                }
            }
    
            static void OnSizeChanged(object sender, SizeChangedEventArgs e)
            {
                Apply(GetSource((FrameworkElement)sender));
            }
    
            static void Apply(FrameworkElement element)
            {
                var container = GetContainer(element);
                if (container == null)
                    return;
    
                var offset = GetOffset(element);
                if (container != element)
                    offset = element.TranslatePoint(offset, container);
                var position = GetPosition(element);
                position -= (Vector)offset;
    
                container.SetValue(Canvas.LeftProperty, position.X);
                container.SetValue(Canvas.TopProperty, position.Y);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-31
      • 1970-01-01
      相关资源
      最近更新 更多