WPF用户控件未更新路径

时间:2016-09-29 13:12:54

标签: c# wpf xaml data-binding user-controls

我在一个更大的项目中找到了一个问题的WPF示例。我有一个名为" UserControl1"的用户控件。数据上下文设置为self,因此我在代码隐藏中定义了依赖项属性。

在控件中我有一个ItemsControl。在ItemsSource中,我有一个CompositeCollection,它包含一个CollectionContainer和一个Line。 Line就是为了证明我在画画。

我还有一个名为" GraphPen"包含PathGeometry依赖项属性。用户控件的CollectionContainer包含这些GraphPens的ObservableCollection。

现在,我有一个" MainWindow"测试用户控件。在MainWindow中我有一个DispatchTimer,在该计时器的Tick事件中,我将LineSegments添加到PathFigure中,该PathFigure已添加到GraphPen的单个实例的PathGeometry的数字集合中。

我希望看到与现有红线平行绘制的对角线,但没有任何显示。如果我在Tick事件处理程序的末尾添加一个断点,我可以检查用户控件并向下钻取并查看线段是否存在。由于某种原因,他们没有被渲染。我怀疑我在绑定方面做错了什么。

我将提供以下代码。

GraphPen.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace WpfExampleControlLibrary
{
    public class GraphPen : DependencyObject
    {
        #region Constructor

        public GraphPen()
        {
            PenGeometry = new PathGeometry();
        }

        #endregion Constructor

        #region Dependency Properties

        // Line Color

        public static PropertyMetadata PenLineColorPropertyMetadata
            = new PropertyMetadata(null);
        public static DependencyProperty PenLineColorProperty
            = DependencyProperty.Register(
                "PenLineColor",
                typeof(Brush),
                typeof(GraphPen),
                PenLineColorPropertyMetadata);
        public Brush PenLineColor
        {
            get { return (Brush)GetValue(PenLineColorProperty); }
            set { SetValue(PenLineColorProperty, value); }
        }

        // Line Thickness

        public static PropertyMetadata PenLineThicknessPropertyMetadata
            = new PropertyMetadata(null);
        public static DependencyProperty PenLineThicknessProperty
            = DependencyProperty.Register(
                "PenLineThickness",
                typeof(Int32),
                typeof(GraphPen),
                PenLineThicknessPropertyMetadata);
        public Int32 PenLineThickness
        {
            get { return (Int32)GetValue(PenLineThicknessProperty); }
            set { SetValue(PenLineThicknessProperty, value); }
        }

        // Pen Geometry

        public static PropertyMetadata PenGeometryMetadata = new PropertyMetadata(null);
        public static DependencyProperty PenGeometryProperty
            = DependencyProperty.Register(
                "PenGeometry",
                typeof(PathGeometry),
                typeof(UserControl1),
                PenGeometryMetadata);

        public PathGeometry PenGeometry
        {
            get { return (PathGeometry)GetValue(PenGeometryProperty); }
            set { SetValue(PenGeometryProperty, value); }
        }

        #endregion Dependency Properties
    }
}

UserControl1.xaml

<UserControl Name="ExampleControl"
             x:Class="WpfExampleControlLibrary.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfExampleControlLibrary"
             DataContext="{Binding RelativeSource={RelativeSource Self}}"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>

        <DataTemplate DataType="{x:Type local:GraphPen}">
            <Path Stroke="{Binding Path=PenLineColor}"
                  StrokeThickness="{Binding Path=PenLineThickness}"
                  Data="{Binding Path=Geometry}">
            </Path>
        </DataTemplate>

    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <ItemsControl Grid.Column="0" Grid.Row="0">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas Background="Aquamarine">
                        <Canvas.LayoutTransform>
                            <ScaleTransform ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5"/>
                        </Canvas.LayoutTransform>
                    </Canvas>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemsSource>
                <CompositeCollection>
                    <CollectionContainer
                        Collection="{
                            Binding Source={RelativeSource Self}, 
                            Path=GraphPens,
                            Mode=OneWay}"/>
                    <Line X1="10" Y1="0" X2="200" Y2="180" Stroke="DarkRed" StrokeThickness="2"/>
                </CompositeCollection>
            </ItemsControl.ItemsSource>
        </ItemsControl>
        <TextBox x:Name="debug" Grid.Column="0" Grid.Row="1" Text="{Binding Path=DebugText}"/>
    </Grid>
</UserControl>

UserControl1.xaml.cs

namespace WpfExampleControlLibrary
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();

            GraphPens = new ObservableCollection<GraphPen>();
        }

        #region Dependency Properties

        // Pens

        public static PropertyMetadata GraphPenMetadata = new PropertyMetadata(null);
        public static DependencyProperty GraphPensProperty
            = DependencyProperty.Register(
                "GraphPens",
                typeof(ObservableCollection<GraphPen>),
                typeof(UserControl1),
                GraphPenMetadata);

        public ObservableCollection<GraphPen> GraphPens
        {
            get { return (ObservableCollection<GraphPen>)GetValue(GraphPensProperty); }
            set { SetValue(GraphPensProperty, value); }
        }

        // Debug Text

        public static PropertyMetadata DebugTextMetadata = new PropertyMetadata(null);
        public static DependencyProperty DebugTextProperty
            = DependencyProperty.Register(
                "DebugText",
                typeof(string),
                typeof(UserControl1),
                DebugTextMetadata);

        public string DebugText
        {
            get { return (string)GetValue(DebugTextProperty); }
            set { SetValue(DebugTextProperty, value); }
        }

        #endregion Dependency Properties
    }
}

MainWindow.xaml

<Window x:Class="POC_WPF_UserControlExample.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:Exmpl="clr-namespace:WpfExampleControlLibrary;assembly=WpfExampleControlLibrary"
        xmlns:local="clr-namespace:POC_WPF_UserControlExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="550" Width="550">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition />
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="100" />
            <RowDefinition />
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <Exmpl:UserControl1 Grid.Column="1" Grid.Row="1" x:Name="myExample"/>
    </Grid>
</Window>

MainWindow.xaml.cs

namespace POC_WPF_UserControlExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private DispatcherTimer _timer = null;
        private GraphPen _graphPen0 = null;
        private Int32 _pos = 0;
        private PathFigure _pathFigure = null;

        public MainWindow()
        {
            InitializeComponent();

            _graphPen0 = new GraphPen();
            _graphPen0.PenLineColor = Brushes.DarkGoldenrod;
            _graphPen0.PenLineThickness = 2;
            myExample.GraphPens.Add(_graphPen0);

            _timer = new DispatcherTimer();
            _timer.Tick += Timer_Tick;
            _timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            _timer.Start();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            _pos++;
            Point penPoint0 = new Point(_pos, _pos + 20);
            if (_graphPen0.PenGeometry.Figures.Count == 0)
            {
                _pathFigure = new PathFigure();
                _graphPen0.PenGeometry.Figures.Add(_pathFigure);
                _pathFigure.StartPoint = penPoint0;
            }
            else
            {
                LineSegment segment = new LineSegment(penPoint0, false);
                _pathFigure.Segments.Add(segment);
            }
            myExample.DebugText = _pos.ToString();
        }
    }
}

屏幕截图

The line being drawn should be parallel to the red line

1 个答案:

答案 0 :(得分:1)

我不需要INotifyPropertyChanged,我也不需要重新创建PenGeometry。对不起,我浪费了你的时间来处理这些想法。

我有你的代码图......某事。一条生长的线。我不知道它是否正在绘制你想要的东西,但你现在可以弄清楚那部分你可以看到绘图的内容。

首先,GraphPen.cs中的次要复制/粘贴错误:

    public static DependencyProperty PenGeometryProperty
        = DependencyProperty.Register(
            "PenGeometry",
            typeof(PathGeometry),
            typeof(UserControl1),
            PenGeometryMetadata);

所有者类型参数必须为GraphPen,而不是UserControl1

            typeof(PathGeometry),
            typeof(GraphPen),
            PenGeometryMetadata);

第二:在UserControl1中绑定。您对Self的绑定不起作用,因为在这种情况下,Self是您绑定的CollectionContainer。通常您使用RelativeSource={RelativeSource AncestorType=UserControl}的来源,但CollectionContainer不在视觉树中,因此无法正常工作(真实直观,是吧?)。相反,我们使用BindingProxy(要遵循的来源):

<UserControl.Resources>
    <!-- ... stuff ... -->

    <local:BindingProxy 
        x:Key="UserControlBindingProxy"
        Data="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}" 
        />
</UserControl.Resources>

对于收集容器......

<CollectionContainer
    Collection="{
        Binding 
        Source={StaticResource UserControlBindingProxy},
        Path=Data.GraphPens,
        Mode=OneWay}"
    />

请注意我们绑定Data.GraphPens; Data是代理的目标。

另外,ItemTemplate需要ItemsControl,因为它不知道如何显示GraphPen

<ItemsControl.ItemTemplate>
    <DataTemplate DataType="local:GraphPen">
        <Border >
            <Path
                Data="{Binding PenGeometry}"
                StrokeThickness="{Binding PenLineThickness}"
                Stroke="{Binding PenLineColor, PresentationTraceSources.TraceLevel=None}"
                />
        </Border>
    </DataTemplate>
</ItemsControl.ItemTemplate>

注意PresentationTraceSources.TraceLevel=None。将None更改为High,它会在VS输出窗格中为您提供有关Binding的大量调试信息。我补充说,当我试图弄清楚为什么我在构造函数中将PenLineColor设置为Brushes.Black时,但它在运行时不断出现DarkGoldenrod。你能说 duhhh? Duhhh!

现在是绑定代理。这样做是因为您要将任何随机对象用作DataContext,将其绑定到Data定义为资源的BindingProxy实例上,并且您已获得该DataContext StaticResource可通过您可以使用RelativeSource获得的资源获得。如果您在某个地方通过using System.Windows; namespace WpfExampleControlLibrary { public class BindingProxy : Freezable { #region Overrides of Freezable protected override Freezable CreateInstanceCore() { return new BindingProxy(); } #endregion public object Data { get { return (object)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } // Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc... public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); } } 的可视树来获取某些内容,那么您可以依赖它。

BindingProxy.cs

true

最后,在MainWindow中,您需要在isStroked个实例上传递LineSegment LineSegment segment = new LineSegment(penPoint0, true);

Path

否则他们不会被抽出。

所以现在它回到了你的腿上,温暖的黄金棒和舒缓的海蓝宝石。 Ave,imperator 等等。

由原作者编辑!

感谢Ed的所有努力。

  1. 我无法相信我错过了UserControl1 - &gt; GraphPen
  2. BindingProxy非常方便
  3. TraceLevel也很方便,我之前没用过
  4. 我还纠正了DebugText绑定,现在可以正常工作了
      

    我从来没有注意到这一点!

  5. 在DataTemplate中,为什么我们需要将Path包装在Border中?
      

    我们没有。在我显示Border之前,我补充说,在模板中有一些保证可见的东西。它最初有绿色边框。我删除了这些属性,但忘记删除GraphPen本身。

  6. 如果您查看我的Timer_Tick,您会注意到现在我需要更新的是添加新的段。希望这有助于提升绩效。你的意见?
      

    不知道。我实际上会将AddSegment(Point pt)中的代码添加为AddSegment(float x, float y) => AddSegment(new Point(x,y));if重载。我对在事件处理程序中放置逻辑非常过敏。我做的最多的事情是围绕一个执行实际工作的非处理程序方法抛出try/catchAddSegment(Point pt)。然后,我将两种方式都写<UserControl Name="ExampleControl" x:Class="WpfExampleControlLibrary.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfExampleControlLibrary" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <UserControl.Resources> <local:BindingProxy x:Key="UserControlBindingProxy" Data="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"/> <DataTemplate DataType="{x:Type local:GraphPen}"> <Border> <Path Data="{Binding PenGeometry}" StrokeThickness="{Binding PenLineThickness}" Stroke="{Binding PenLineColor, PresentationTraceSources.TraceLevel=None}" /> </Border> </DataTemplate> </UserControl.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="40"/> </Grid.RowDefinitions> <ItemsControl Grid.Column="0" Grid.Row="0"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas Background="Aquamarine"> <Canvas.LayoutTransform> <ScaleTransform ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5"/> </Canvas.LayoutTransform> </Canvas> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemsSource> <CompositeCollection> <CollectionContainer Collection="{Binding Source={StaticResource UserControlBindingProxy}, Path=Data.GraphPens, Mode=OneWay}"/> <Line X1="10" Y1="0" X2="200" Y2="180" Stroke="DarkRed" StrokeThickness="2"/> </CompositeCollection> </ItemsControl.ItemsSource> </ItemsControl> <TextBox x:Name="debug" Grid.Column="0" Grid.Row="1" Text="{Binding Source={StaticResource UserControlBindingProxy}, Path=Data.DebugText, Mode=OneWay}"/> </Grid> </UserControl> ,并将一种方式与另一种方式进行基准测试。

  7. 我将添加完整性代码:

    UserControl1.xaml

    namespace WpfExampleControlLibrary
    {
        /// <summary>
        /// Interaction logic for UserControl1.xaml
        /// </summary>
        public partial class UserControl1 : UserControl
        {
            #region Constructor
    
            public UserControl1()
            {
                InitializeComponent();
    
                GraphPens = new ObservableCollection<GraphPen>();
            }
    
            #endregion Constructor
    
            #region Public Methods
    
            #endregion Public Methods
    
            #region Dependency Properties
    
            // Pens
    
            public static PropertyMetadata GraphPenMetadata = new PropertyMetadata(null);
            public static DependencyProperty GraphPensProperty
                = DependencyProperty.Register(
                    "GraphPens",
                    typeof(ObservableCollection<GraphPen>),
                    typeof(UserControl1),
                    GraphPenMetadata);
    
            public ObservableCollection<GraphPen> GraphPens
            {
                get { return (ObservableCollection<GraphPen>)GetValue(GraphPensProperty); }
                set { SetValue(GraphPensProperty, value); }
            }
    
            // Debug Text
    
            public static PropertyMetadata DebugTextMetadata = new PropertyMetadata(null);
            public static DependencyProperty DebugTextProperty
                = DependencyProperty.Register(
                    "DebugText",
                    typeof(string),
                    typeof(UserControl1),
                    DebugTextMetadata);
    
            public string DebugText
            {
                get { return (string)GetValue(DebugTextProperty); }
                set { SetValue(DebugTextProperty, value); }
            }
    
            #endregion Dependency Properties
        }
    }
    

    UserControl1.xaml.cs

    namespace WpfExampleControlLibrary
    {
        public class GraphPen : DependencyObject
        {
            #region Constructor
    
            public GraphPen()
            {
                PenGeometry = new PathGeometry();
            }
    
            #endregion Constructor
    
            #region Dependency Properties
    
            // Line Color
    
            public static PropertyMetadata PenLineColorPropertyMetadata
                = new PropertyMetadata(null);
            public static DependencyProperty PenLineColorProperty
                = DependencyProperty.Register(
                    "PenLineColor",
                    typeof(Brush),
                    typeof(GraphPen),
                    PenLineColorPropertyMetadata);
            public Brush PenLineColor
            {
                get { return (Brush)GetValue(PenLineColorProperty); }
                set { SetValue(PenLineColorProperty, value); }
            }
    
            // Line Thickness
    
            public static PropertyMetadata PenLineThicknessPropertyMetadata
                = new PropertyMetadata(null);
            public static DependencyProperty PenLineThicknessProperty
                = DependencyProperty.Register(
                    "PenLineThickness",
                    typeof(Int32),
                    typeof(GraphPen),
                    PenLineThicknessPropertyMetadata);
            public Int32 PenLineThickness
            {
                get { return (Int32)GetValue(PenLineThicknessProperty); }
                set { SetValue(PenLineThicknessProperty, value); }
            }
    
            // Pen Geometry
    
            public static PropertyMetadata PenGeometryMetadata = new PropertyMetadata(null);
            public static DependencyProperty PenGeometryProperty
                = DependencyProperty.Register(
                    "PenGeometry",
                    typeof(PathGeometry),
                    typeof(GraphPen),
                    PenGeometryMetadata);
    
            public PathGeometry PenGeometry
            {
                get { return (PathGeometry)GetValue(PenGeometryProperty); }
                set { SetValue(PenGeometryProperty, value); }
            }
    
            #endregion Dependency Properties
        }
    }
    

    GraphPen.cs

    namespace WpfExampleControlLibrary
    {
        public class BindingProxy : Freezable
        {
            #region Override Freezable Abstract Parts
            protected override Freezable CreateInstanceCore()
            {
                return new BindingProxy();
            }
    
            #endregion Override Freezable Abstract Parts
    
            #region Dependency Properties
    
            // Using a DependencyProperty as the backing store for Data.
            // This enables animation, styling, binding, etc...
            public static PropertyMetadata DataMetadata = new PropertyMetadata(null);
            public static readonly DependencyProperty DataProperty
                = DependencyProperty.Register(
                    "Data",
                    typeof(object),
                    typeof(BindingProxy),
                    DataMetadata);
    
            public object Data
            {
                get { return (object)GetValue(DataProperty); }
                set { SetValue(DataProperty, value); }
            }
    
            #endregion Dependency Properties
        }
    }
    

    BindingProxy.cs

    <Window x:Class="POC_WPF_UserControlExample.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:Exmpl="clr-namespace:WpfExampleControlLibrary;assembly=WpfExampleControlLibrary"
            xmlns:local="clr-namespace:POC_WPF_UserControlExample"
            mc:Ignorable="d"
            Title="MainWindow" Height="550" Width="550">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100"/>
                <ColumnDefinition />
                <ColumnDefinition Width="100"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="100" />
                <RowDefinition />
                <RowDefinition Height="100" />
            </Grid.RowDefinitions>
            <Exmpl:UserControl1 Grid.Column="1" Grid.Row="1" x:Name="myExample"/>
        </Grid>
    </Window>
    

    MainWindow.xaml

    namespace POC_WPF_UserControlExample
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            private DispatcherTimer _timer = null;
            private GraphPen _graphPen0 = null;
            private Int32 _pos = 0;
            private PathFigure _pathFigure0 = null;
            private bool _firstTime = true;
    
            public MainWindow()
            {
                InitializeComponent();
    
                _pathFigure0 = new PathFigure();
                _graphPen0 = new GraphPen();
                _graphPen0.PenLineColor = Brushes.DarkGoldenrod;
                _graphPen0.PenLineThickness = 2;
                _graphPen0.PenGeometry = new PathGeometry();
                _graphPen0.PenGeometry.Figures.Add(_pathFigure0);
                myExample.GraphPens.Add(_graphPen0);
    
                _timer = new DispatcherTimer();
                _timer.Tick += Timer_Tick;
                _timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
                _timer.Start();
            }
    
            private void Timer_Tick(object sender, EventArgs e)
            {
                _pos++;
                Point penPoint0 = new Point(_pos, _pos + 20);
                if (_firstTime)
                {
                    myExample.GraphPens[0].PenGeometry.Figures[0].StartPoint = penPoint0;
                    _firstTime = false;
                }
                else
                {
                    LineSegment segment = new LineSegment(penPoint0, true);
                    myExample.GraphPens[0].PenGeometry.Figures[0].Segments.Add(segment);
                }
    
                myExample.DebugText = _pos.ToString();
            }
        }
    }
    

    MainWindow.xaml.cs

    select substr(cast ( 020020 as text ),3) 
    outputs 020
    
    it is ignoring the 3rd char