I have a stripped down WPF example of a problem I am having in a much larger project. I have a user control called "UserControl1". The data context is set to self so I have dependency properties defined in the code-behind.
In the control I have an ItemsControl. In the ItemsSource I have a CompositeCollection that contains a CollectionContainer and a Line. The Line is there just to prove to myself that I am drawing.
I also have an object called "GraphPen" that contains a PathGeometry dependency property. The CollectionContainer of the user control contains an ObservableCollection of these GraphPens.
Now, I have a "MainWindow" to test the user control. In the MainWindow I have a DispatchTimer and in the Tick event of that timer, I add LineSegments to a PathFigure which has been added to the Figures collection of the PathGeometry of the single instance of the GraphPen.
I expect to see a diagonal line being drawn in parallel to the existing red line, but nothing shows up. If I put a break point at the end of the Tick event handler, I can examine the user control and drill down and see that the line segments do exist. For some reason they are not being rendered. I suspect I have done something wrong in the binding.
I will supply the code below.
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();
}
}
}
Screen Shot
I did not need INotifyPropertyChanged
, and I did not need to recreate PenGeometry
. I'm sorry I wasted your time with those ideas.
I've got your code drawing... something. A line that grows. I don't know if it's drawing exactly what you want, but you can figure out that part now that you can see what it is drawing.
First, minor copy/paste error in GraphPen.cs
:
public static DependencyProperty PenGeometryProperty
= DependencyProperty.Register(
"PenGeometry",
typeof(PathGeometry),
typeof(UserControl1),
PenGeometryMetadata);
Owner type parameter needs to be GraphPen
, not UserControl1
:
typeof(PathGeometry),
typeof(GraphPen),
PenGeometryMetadata);
Second: Binding in UserControl1
. Your binding to Self
isn't going to work because Self
, in that case, is the CollectionContainer
you're binding on. Usually you'd use a source of RelativeSource={RelativeSource AncestorType=UserControl}
, but CollectionContainer
is not in the visual tree so that doesn't work (real intuitive, huh?). Instead we use a BindingProxy
(source to follow):
<UserControl.Resources>
<!-- ... stuff ... -->
<local:BindingProxy
x:Key="UserControlBindingProxy"
Data="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</UserControl.Resources>
And for the collection container...
<CollectionContainer
Collection="{
Binding
Source={StaticResource UserControlBindingProxy},
Path=Data.GraphPens,
Mode=OneWay}"
/>
Notice we're binding to Data.GraphPens
; Data
is the target of the proxy.
Also, we need an ItemTemplate
for the ItemsControl
, because it doesn't know how to display a 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>
Note PresentationTraceSources.TraceLevel=None
. Change None
to High
and it'll give you a lot of debugging info about the Binding
in the VS Output pane. I added that when I was trying to figure out why I was setting PenLineColor
to Brushes.Black
in the constructor, but it kept coming out DarkGoldenrod
at runtime. Can you say duhhh? Duhhh!
Now the binding proxy. What that does is you take any random object you want to use as a DataContext
, bind it to Data
on a BindingProxy
instance defined as a resource, and you've got that DataContext
available via a resource that you can get to with a StaticResource
. If you're someplace where you can't get to something via the visual tree with RelativeSource
, it's an option that you can rely on.
BindingProxy.cs
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));
}
}
And finally, in MainWindow, you need to pass true
for isStroked
on the LineSegment
instances:
LineSegment segment = new LineSegment(penPoint0, true);
Otherwise they're not drawn.
So now it's back in your lap, in warm goldenrod and soothing aquamarine. Ave, imperator , and all that.
Edit by original author!
Thank you Ed for all your effort.
I never even noticed that!
We don't. Before I got the
Path
showing, I added that to have something in the template that was guaranteed to be visible . It had a green border originally. I removed those attributes, but forgot to remove theBorder
itself.
No idea. I would actually put that segment adding code in
GraphPen
asAddSegment(Point pt)
andAddSegment(float x, float y) => AddSegment(new Point(x,y));
overloads. I have a great allergy to putting logic in event handlers. The most I'll do is toss anif
or atry/catch
around a non-handler method that does the real work. Then I'd writeAddSegment(Point pt)
both ways and benchmark one against the other.
I will add my code for completeness:
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"
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>
UserControl1.xaml.cs
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
}
}
GraphPen.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
}
}
BindingProxy.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
}
}
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 _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();
}
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.