In WPF I am trying to draw graph lines in a UserControl. I will need to scale the X and Y values by different amounts.
I have a sample project I will include below. The problem is that when I use RenderTransform to scale the graph line, the LineThickness is also scaled. This would normally not be a problem because I could descale the line thickness by the inverse of the scaled value:
LineThickness = desiredLineThickness * (1/scaledValue)
Unfortunately, in this application I will need to scale the X and Y dimensions by very different values, and LineThickness is not separated into X and Y values.
In my example below I am drawing a sine wave that is scaled to the size of the drawing surface of the user control. You can see that the peaks and valleys extend beyond the drawing surface. This is because the original data goes from 1.0 to -1.0 and is scaled to the height of the drawing surface and translated to the middle of the drawing surface.
To demonstrate it more clearly, I also draw a blue line from 0,0 to 90,90 and then from 90,90 horizontally to the right. This line is also scaled to the height of the drawing surface. You can see the horizontal line is very thick.
I need a way to descale these lines in the Y axis separately from the X axis which is not scaled in this case.
I have tried several approaches including making a contained object of lines. I was thinking I could put the endpoints of the lines in the scaled position and then when drawing the actual lines, I would use a render transform to descale the Y values, but I could never get that to work because of the whole binding proxy problem.
Anyone have any ideas or have been faced with this before? I can't be the first with this problem.
Here is the code:
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
private void DrawingSurface_SizeChanged(object sender, SizeChangedEventArgs e)
{
foreach (GraphPen graphPen in GraphPens)
{
graphPen.SetDrawingDimensions(e.NewSize.Height, e.NewSize.Width);
}
}
}
}
UserControl.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}">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.RenderTransform>
<TransformGroup>
<TransformGroup.Children>
<ScaleTransform
CenterX="0.0"
CenterY="0.0"
ScaleX="1.0"
ScaleY="{Binding ScaleHeight}"/>
<TranslateTransform
X="0.0"
Y="{Binding TranslateHeight}"/>
</TransformGroup.Children>
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<Path
Data="{Binding PenGeometry}"
StrokeThickness="{Binding PenLineThickness}"
Stroke="{Binding
PenLineColor,
PresentationTraceSources.TraceLevel=None}"
/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<ItemsControl x:Name="DrawingSurface" Grid.Column="0" Grid.Row="0" SizeChanged="DrawingSurface_SizeChanged">
<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>
GraphPen.cs:
namespace WpfExampleControlLibrary
{
public class GraphPen : DependencyObject
{
private double _drawingSurfaceHeight = 100;
#region Constructor
public GraphPen()
{
PenGeometry = new PathGeometry();
PenGeometry.Figures.Add(new PathFigure());
}
#endregion Constructor
#region Dependency Properties
// LineColor
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); }
}
// LineThickness
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); }
}
// PenYMin
public static PropertyMetadata PenYMinPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenYMinProperty
= DependencyProperty.Register(
"PenYMin",
typeof(Double),
typeof(GraphPen),
PenYMinPropertyMetadata);
public double PenYMin
{
get { return (double)GetValue(PenYMinProperty); }
set { SetValue(PenYMinProperty, value); }
}
// PenYMax
public static PropertyMetadata PenYMaxPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenYMaxProperty
= DependencyProperty.Register(
"PenYMax",
typeof(Double),
typeof(GraphPen),
PenYMaxPropertyMetadata);
public double PenYMax
{
get { return (double)GetValue(PenYMaxProperty); }
set { SetValue(PenYMaxProperty, value); }
}
// ScaleHeight
public static PropertyMetadata ScaleHeightPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty ScaleHeightProperty
= DependencyProperty.Register(
"ScaleHeight",
typeof(Double),
typeof(GraphPen),
ScaleHeightPropertyMetadata);
public double ScaleHeight
{
get { return (double)GetValue(ScaleHeightProperty); }
set { SetValue(ScaleHeightProperty, value); }
}
// TranslateHeight
public static PropertyMetadata TranslateHeightPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty TranslateHeightProperty
= DependencyProperty.Register(
"TranslateHeight",
typeof(Double),
typeof(GraphPen),
TranslateHeightPropertyMetadata);
public double TranslateHeight
{
get { return (double)GetValue(TranslateHeightProperty); }
set { SetValue(TranslateHeightProperty, 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
public void SetDrawingDimensions(double height, double width)
{
double dataHeight;
_drawingSurfaceHeight = height;
dataHeight = PenYMax - PenYMin;
ScaleHeight = _drawingSurfaceHeight / dataHeight;
TranslateHeight = ScaleHeight * -PenYMin;
}
}
}
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 GraphPen _graphPen1 = null;
private Int32 _pos = 0;
private bool _firstTime = true;
public MainWindow()
{
InitializeComponent();
_graphPen0 = new GraphPen();
_graphPen0.PenLineColor = Brushes.DarkGoldenrod;
_graphPen0.PenLineThickness = 1;
_graphPen0.PenYMax = 1.0;
_graphPen0.PenYMin = -1.0;
myExample.GraphPens.Add(_graphPen0);
_graphPen1 = new GraphPen();
_graphPen1.PenLineColor = Brushes.DarkBlue;
_graphPen1.PenLineThickness = 1;
_graphPen1.PenYMax = 10.0;
_graphPen1.PenYMin = 0.0;
myExample.GraphPens.Add(_graphPen1);
_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, Math.Sin(_pos % 360));
Point penPoint1 = new Point(0,0);
if (_pos <= 9) penPoint1 = new Point(_pos, _pos);
if (_pos > 9) penPoint1 = new Point(_pos, 9);
if (_firstTime)
{
myExample.GraphPens[0].PenGeometry.Figures[0].StartPoint = penPoint0;
myExample.GraphPens[1].PenGeometry.Figures[0].StartPoint = penPoint1;
_firstTime = false;
}
else
{
LineSegment segment0 = new LineSegment(penPoint0, true);
myExample.GraphPens[0].PenGeometry.Figures[0].Segments.Add(segment0);
LineSegment segment1 = new LineSegment(penPoint1, true);
myExample.GraphPens[1].PenGeometry.Figures[0].Segments.Add(segment1);
}
myExample.DebugText = _pos.ToString();
}
}
}
EDIT by poster
I forgot to show BindingProcy.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
}
}
EDIT by poster
Here is a screen shot:
Related
I have got an usercontrol with some dependency property. One of them (ValueProperty) has got a PropertyChangedCallback but it never run.
namespace test
{
public partial class IndicatorLigth : UserControl
{
public IndicatorLigth()
{
InitializeComponent();
DataContext = this;
CurrentBrush = new SolidColorBrush(InactiveColor);
lIndicator.Background = CurrentBrush;
TurnOnValue = true;
Value = true;
}
public static readonly DependencyProperty ActiveColorProperty =
DependencyProperty.Register("ActiveColor", typeof(Color), typeof(IndicatorLigth), new UIPropertyMetadata(Colors.Green));
public Color ActiveColor
{
get { return (Color)GetValue(ActiveColorProperty); }
set { SetValue(ActiveColorProperty, value); }
}
public static readonly DependencyProperty InactiveColorProperty =
DependencyProperty.Register("InactiveColor", typeof(Color), typeof(IndicatorLigth), new UIPropertyMetadata(Colors.Red));
public Color InactiveColor
{
get { return (Color)GetValue(InactiveColorProperty); }
set { SetValue(InactiveColorProperty, value); }
}
private SolidColorBrush _currentBrush;
public SolidColorBrush CurrentBrush
{
get { return _currentBrush; }
set { _currentBrush = value; }
}
public static readonly DependencyProperty TurnOnValueProperty =
DependencyProperty.Register("TurnOnValue", typeof(bool), typeof(IndicatorLigth), new UIPropertyMetadata(true));
public bool TurnOnValue
{
get { return (bool)GetValue(TurnOnValueProperty); }
set { SetValue(TurnOnValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(bool), typeof(IndicatorLigth),
new FrameworkPropertyMetadata(true,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSetColorChanged));
public bool Value
{
get { return (bool)GetValue(ValueProperty); }
set
{
SetValue(ValueProperty, value);
}
}
private void CheckStatus(bool sign)
{
if (sign == TurnOnValue)
CurrentBrush = new SolidColorBrush(ActiveColor);
else CurrentBrush = new SolidColorBrush(InactiveColor);
}
private static void OnSetColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IndicatorLigth mycontrol = d as IndicatorLigth;
mycontrol.callmyInstanceMethod(e);
}
private void callmyInstanceMethod(DependencyPropertyChangedEventArgs e)
{
CheckStatus((bool)e.NewValue);
lIndicator.Background = CurrentBrush;
}
}
}
And XAML where I use my usercontrol (I use it in another UserControl):
<UserControl
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:local="clr-namespace:test"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" >
...
<StackPanel Orientation="Vertical">
<Label Content="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=DataContext.Sign}"/>
<StackPanel>
<local:IndicatorLigth ActiveColor="Thistle" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=DataContext.Sign}"/>
</StackPanel>
</StackPanel>
The Sign parameter belongs to an IsEnabled bindable property of a ComboBox which not in the XAML code. The label content is correct, it changes when I change combobox enabled status, but my UserControl setter of Value, OnSetColorChanged and callmyInstanceMethod don't fire. Could you tell me what wrong in my code? Thank you very much.
Update: So I was wrong. The code mentioned above is correct. The problem will be occures when I push the stackpanel into a devexpress LayoutGroup HeaderTemplate:
<dxlc:LayoutGroup Orientation="Vertical" VerticalAlignment="Top">
<dxlc:LayoutGroup.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Label Content="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=DataContext.Sign}"/>
<StackPanel>
<local:IndicatorLigth ActiveColor="Thistle" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=DataContext.Sign}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</dxlc:LayoutGroup.HeaderTemplate>
</dxlc:LayoutGroup>
Sorry for disturbing you and thank you for advices. I have found the cause of problem. I needn't use <HeaderTemplate><DataTemplate>, I have to use simple <Header> block :)
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 can't believe I missed the UserControl1 -> GraphPen
The BindingProxy will be very handy
The TraceLevel will also be handy, I had not used that before
I also corrected the DebugText binding and now that works
I never even noticed that!
In the DataTemplate why did we need to wrap the Path in a Border?
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 the Border itself.
If you look in my Timer_Tick you will note that now all I have to update is adding new segments. Hopefully, this will help performance. Your opinion?
No idea. I would actually put that segment adding code in GraphPen as AddSegment(Point pt) and AddSegment(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 an if or a try/catch around a non-handler method that does the real work. Then I'd write AddSegment(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();
}
}
}
I'm currently trying to implement some kind of camera-logic for a schoolproject( 2D Multiplayer-PacMan) using WPF(first time im using wpf). We have a Tile-Based Walkmap using a Canvas and an ItemControl, which is bigger than the actual screensize:
GameView.xaml
<controls:Camera HorizontalOffset="{Binding xPos}" VerticalOffset="{Binding yPos}">
<ItemsControl ItemsSource="{Binding Tiles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding ImagePath}" Width="{Binding Size}" Height="{Binding Size}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</controls:Camera>
I'm trying to use a ScrollViewer for my Camera, but as you all know we can't bind The Horizontal and Vertical Offset to Properties due to the fact that they are readonly.
Thats why I created a UserControl named "Camera" which has a ScrollViewer and two DependencyProperties for Binding.
Camera.xaml
<UserControl x:Class="PacmanClient.UserControls.Camera"
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:PacmanClient.UserControls"
mc:Ignorable="d"
d:DesignHeight="1600" d:DesignWidth="1900">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<ScrollViewer Name="cameraViewer" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" IsEnabled="True">
<ContentPresenter/>
</ScrollViewer>
</ControlTemplate>
</UserControl.Template>
</UserControl>
Camera.xaml.cs
public partial class Camera : UserControl
{
ScrollViewer cameraViewer;
public Camera()
{
InitializeComponent();
}
#region HorizontalOffset
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
cameraViewer = this.Template.FindName("cameraViewer", this) as ScrollViewer;
}
public double HorizontalOffset
{
get
{
return (double)GetValue(HorizontalOffsetProperty);
}
set
{
SetValue(HorizontalOffsetProperty, value);
OnHorizontalOffsetChanged(value);
}
}
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached("HorizontalOffset", typeof(double), typeof(Camera),
new UIPropertyMetadata(0.0));
private void OnHorizontalOffsetChanged(double value)
{
cameraViewer.ScrollToHorizontalOffset(value);
}
#endregion
#region VerticalOffset
public double VerticalOffset
{
get
{
return (double)GetValue(VerticalOffsetProperty);
}
set
{
SetValue(VerticalOffsetProperty, value);
OnVerticalOffsetChanged(value);
}
}
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(Camera),
new UIPropertyMetadata(0.0));
private void OnVerticalOffsetChanged(double value)
{
cameraViewer.ScrollToVerticalOffset(value);
}
#endregion
}
Now I'm having two problems.
First:
When im trying to use my UserControl(as seen in GameView.xaml) and bind some Properties to the DependencyProperties, I'm getting the Error that those members are not recognized or accessible.( I actually thought i fixed this, but now it's back.) This has to be an AccessProblem, because autocompletion actually suggests me HorinzontalOffset and VerticalOffset.
I just can't find a solution.
And Second:
In the Version where I was able to access those Properties and successfully bind some Properties to them, the Values of the DependencyProperties never changed when the properties bound to them changed. I checked it via debugging and the setter of the code behind property is never called.
I'm hoping you can help me with those problems, i have no idea why it isn't working.
[Edit]
MainWindow.Xaml
<Window x:Class="WpfApplication3.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:WpfApplication3"
xmlns:test="clr-namespace:test"
mc:Ignorable="d"
Title="MainWindow" Height="1600" Width="1900">
<StackPanel>
<Button Content="yolo" Click="Button_Click"></Button>
<ScrollViewer Name ="hallo" Height="1600" Width="1600" test:ScrollViewerExtension.HorizontalOffset = "{Binding xPos}" test:ScrollViewerExtension.VerticalOffset="{Binding yPos}" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Canvas Height="3000" Width="3000">
<Ellipse Name="e1" Height="42" Width="42" Fill="Yellow"></Ellipse>
</Canvas>
</ScrollViewer>
</StackPanel>
</Window>
Mainwindow.cs
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public double xPos
{
get;
set;
}
public double yPos
{
get;
set;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
xPos += 50.0;
yPos += 50.0;
Canvas.SetTop(e1, yPos);
Canvas.SetLeft(e1, xPos);
}
}
}
If you craete custom control, you should create DependencyProperty. You created AttachedProperty, that's something else.
I will show you, how to use attached property:
<ScrollViewer x:Name="ScrollViewer1" Height="100" Width="150"
HorizontalScrollBarVisibility="Auto"
local:ScrollViewerExtension.HorizontalOffset="{Binding Value, ElementName=Slider1}">
<Rectangle Height="80" Width="100" Margin="100,50,0,0" Fill="Red"/>
</ScrollViewer>
<!-- you can databind ScrollViewerExtension.HorizontalOffset to whatever,
Slider is just for demonstration purposes -->
<Slider x:Name="Slider1"
Maximum="{Binding ElementName=ScrollViewer1, Path=ScrollableWidth}" />
and the attached property definition:
public static class ScrollViewerExtension
{
public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.RegisterAttached("HorizontalOffset", typeof (double), typeof (ScrollViewerExtension),
new PropertyMetadata(HorizontalOffsetChanged));
private static void HorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var scrollViewer = (ScrollViewer) d;
scrollViewer.ScrollToHorizontalOffset((double)e.NewValue);
}
public static void SetHorizontalOffset(DependencyObject element, double value)
{
element.SetValue(HorizontalOffsetProperty, value);
}
public static double GetHorizontalOffset(DependencyObject element)
{
return (double) element.GetValue(HorizontalOffsetProperty);
}
}
as you can see, attached properties should be used with existing controls. If you create new control, use dependency properties
Well, let's see if this helps you. Your first problem is difficult to solve without seeing your full code, because what you have shown here does not have any problem (in fact, i made a test solution copying/pasting this code and it worked). The second problem has to do with how you have defined the attached properties.
In the third parameter of RegisterAttached you should add a callback to your propertyChanged event handling method, like this:
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached("HorizontalOffset", typeof(object), typeof(Camera),new UIPropertyMetadata(null, HorizontalOffsetPropertyChanged));
Also you need to add two methods for getting/setting this property value:
public static object GetHorizontalOffset(DependencyObject obj)
{
return (string)obj.GetValue(HorizontalOffsetProperty);
}
public static void SetHorizontalOffset(DependencyObject obj, object value)
{
obj.SetValue(HorizontalOffsetProperty, value);
}
This is the full code for your usercontrol:
public partial class Camera : UserControl
{
public Camera()
{
InitializeComponent();
}
#region HorizontalOffset
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached("HorizontalOffset", typeof(object), typeof(Camera),new UIPropertyMetadata(null, HorizontalOffsetPropertyChanged));
public static object GetHorizontalOffset(DependencyObject obj)
{
return (string)obj.GetValue(HorizontalOffsetProperty);
}
public static void SetHorizontalOffset(DependencyObject obj, object value)
{
obj.SetValue(HorizontalOffsetProperty, value);
}
public static void HorizontalOffsetPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
Camera cam = o as Camera;
ScrollViewer scroll=cam.Template.FindName("cameraViewer", cam) as ScrollViewer;
double horizontal = 0;
if (e.NewValue is double)
{
horizontal =(double) e.NewValue;
}
scroll.ScrollToHorizontalOffset(horizontal);
}
#endregion
#region VerticalOffset
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(object), typeof(Camera), new UIPropertyMetadata(null, VerticalOffsetPropertyChanged));
public static object GetVerticalOffset(DependencyObject obj)
{
return (string)obj.GetValue(VerticalOffsetProperty);
}
public static void SetVerticalOffset(DependencyObject obj, object value)
{
obj.SetValue(VerticalOffsetProperty, value);
}
public static void VerticalOffsetPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
Camera cam = o as Camera;
ScrollViewer scroll = cam.Template.FindName("cameraViewer", cam) as ScrollViewer;
double vertical = 0;
if (e.NewValue is double)
{
vertical = (double)e.NewValue;
}
scroll.ScrollToVerticalOffset(vertical);
}
#endregion
}
Hope this gets you on the track
I have code below inside Grid:
<Grid.RenderTransform>
<TranslateTransform
X="{Binding X, Converter={StaticResource HorizontalPositionConverter}}"
Y="{Binding Y, Converter={StaticResource VerticalPositionConverter}}"
/>
</Grid.RenderTransform>
How can I get binding of TranslateTransform.X or TranslateTransform.Y in code behind? I found this question but solution works for non-nested dependency properties. What to do when they are? I cannot consider binding to entire RenderTransform. I am developing winrt app, so multibinding is out of the game.
Here is the code behind binding. I didn't use a converter because my X and Y are defined double. In order to make a correct binding you have to use dependecy property or another notification mechanism (like INotifyPropertyChanged implementation). Here is code behind binding solution (not MVVM). I've added the button to test the moving.
1. XAML code:
<Window x:Class="TransformBindingSoHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RenderTransform>
<TranslateTransform
X="{Binding ElementName=This, Path=X, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Y="{Binding ElementName=This, Path=Y, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Grid.RenderTransform>
<Button Content="Click" Width="100" Height="100" Click="ButtonBase_OnClick"></Button>
</Grid>
2. Code behind :
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty XProperty = DependencyProperty.Register(
"X", typeof (double), typeof (MainWindow), new PropertyMetadata(default(double)));
public double X
{
get { return (double) GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public static readonly DependencyProperty YProperty = DependencyProperty.Register(
"Y", typeof (double), typeof (MainWindow), new PropertyMetadata(default(double)));
private static double _position;
public double Y
{
get { return (double) GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
X = ++_position;
Y = _position;
}
}
Update 1:
Here is code-behind based solution, there is no binding in XAML:
3. Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty XProperty = DependencyProperty.Register(
"X", typeof (double), typeof (MainWindow), new PropertyMetadata(default(double)));
public double X
{
get { return (double) GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public static readonly DependencyProperty YProperty = DependencyProperty.Register(
"Y", typeof (double), typeof (MainWindow), new PropertyMetadata(default(double)));
private static double _position;
public double Y
{
get { return (double) GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
X = ++_position;
Y = _position;
}
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var grid = sender as Grid;
if(grid == null) return;
var transform = grid.RenderTransform as TranslateTransform;
if (transform == null)
{
transform = InitTransformBinding();
grid.RenderTransform = transform;
}
else
{
InitTransformBinding(transform);
}
}
private TranslateTransform InitTransformBinding(TranslateTransform t = null)
{
var transform = t ?? new TranslateTransform();
var xBinding = new Binding();
xBinding.Source = this;
xBinding.Path = new PropertyPath("X");
xBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
xBinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(transform, TranslateTransform.XProperty, xBinding);
var yBinding = new Binding();
yBinding.Source = this;
yBinding.Path = new PropertyPath("Y");
yBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
yBinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(transform, TranslateTransform.YProperty, yBinding);
return transform;
}
}
4. XAML code:
<Window x:Class="TransformBindingSoHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Loaded="FrameworkElement_OnLoaded">
<Button Content="Click" Width="100" Height="100" Click="ButtonBase_OnClick"></Button>
</Grid>
Update 2, here on each button click you will scale the grid.
5. Xaml code:
Window x:Class="TransformBindingSoHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:transformBindingSoHelpAttempt="clr-namespace:TransformBindingSoHelpAttempt"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<transformBindingSoHelpAttempt:MainViewModel/>
</Window.DataContext>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type transformBindingSoHelpAttempt:ItemDataContext}">
<Grid>
<Grid.RenderTransform>
<ScaleTransform
ScaleX="{Binding X, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ScaleY="{Binding Y, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid.RenderTransform>
<Button Content="{Binding ButtonContent}" Command="{Binding ButtonCommand}"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
6. View models:
public class MainViewModel:BaseObservableObject
{
public MainViewModel()
{
Items = new ObservableCollection<ItemDataContext>(new List<ItemDataContext>
{
new ItemDataContext{ButtonContent = "A", X = 1.0, Y = 1.0},
new ItemDataContext{ButtonContent = "B", X = 1.0, Y = 1.0},
new ItemDataContext{ButtonContent = "C", X = 1.0, Y = 1.0},
new ItemDataContext{ButtonContent = "D", X = 1.0, Y = 1.0},
});
}
public ObservableCollection<ItemDataContext> Items { get; set; }
}
public class ItemDataContext:BaseObservableObject
{
private ICommand _buttonCommand;
private object _buttonContent;
private double _x;
private double _y;
public double X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged();
}
}
public double Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged();
}
}
public ICommand ButtonCommand
{
get { return _buttonCommand ?? (_buttonCommand = new DelegateCommand(Target)); }
}
public object ButtonContent
{
get { return _buttonContent; }
set
{
_buttonContent = value;
OnPropertyChanged();
}
}
private void Target(object obj)
{
X += 0.2;
Y += 0.2;
}
}
7. How it is looks like:
Please keep in mind that the last update solution is based on LayouTransform and re-build the view on each button click (makes it to be scaled).
Regards,
I'd like to learn how to make a custom border that has a unique shape. I'm using .Net 4.0 and I already have a geometry defined based on a path. I don't know how to do all the programming required to clip the child control of the border, resize everything, etc...
I found this answer which has a link to a blog post explaining how this is done, but the explanation is sparse and the source code download link is broken. So in effect, this is not an answered question: WPF - How Can I make a custom Border
Here's the solution I have so far but the border doesn't resize to fit the child control as you would expect:
public class TrapezoidBorder : Border
{
public static readonly DependencyProperty GlowBrushProperty =
DependencyProperty.Register("GlowBrush", typeof(Brush), typeof(TrapezoidBorder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(double), typeof(TrapezoidBorder), new FrameworkPropertyMetadata(8.0, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty OuterGlowBrushProperty =
DependencyProperty.Register("OuterGlowBrush", typeof(Brush), typeof(TrapezoidBorder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty RenderIsMouseOverProperty =
DependencyProperty.Register("RenderIsMouseOver", typeof(bool), typeof(TrapezoidBorder), new PropertyMetadata(false, new PropertyChangedCallback(OnRenderIsMouseOverChanged)));
public static readonly DependencyProperty RenderIsPressedProperty =
DependencyProperty.Register("RenderIsPressed", typeof(bool), typeof(TrapezoidBorder), new PropertyMetadata(false, new PropertyChangedCallback(OnRenderIsPressedChanged)));
public static readonly DependencyProperty BorderWidthProperty =
DependencyProperty.Register("BorderWidth", typeof(double), typeof(TrapezoidBorder), new FrameworkPropertyMetadata(2.0, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty BorderBrushProperty =
DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(TrapezoidBorder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register("Background", typeof(Brush), typeof(TrapezoidBorder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public Brush Background
{
get { return (Brush)GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
public Brush BorderBrush
{
get { return (Brush)GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }
}
public double BorderWidth
{
get { return (double)GetValue(BorderWidthProperty); }
set { SetValue(BorderWidthProperty, value); }
}
public bool RenderIsPressed
{
get { return (bool)GetValue(RenderIsPressedProperty); }
set { SetValue(RenderIsPressedProperty, value); }
}
public bool RenderIsMouseOver
{
get { return (bool)GetValue(RenderIsMouseOverProperty); }
set { SetValue(RenderIsMouseOverProperty, value); }
}
public Brush OuterGlowBrush
{
get { return (Brush)GetValue(OuterGlowBrushProperty); }
set { SetValue(OuterGlowBrushProperty, value); }
}
public double CornerRadius
{
get { return (double)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public Brush GlowBrush
{
get { return (Brush)GetValue(GlowBrushProperty); }
set { SetValue(GlowBrushProperty, value); }
}
protected override void OnRender(DrawingContext drawingContext)
{
var rc = new Rect(0, 0, this.ActualWidth, this.ActualHeight);
var path = new Path();
//path.Stroke = Brushes.Black;
//path.Data =
// Geometry.Parse(
// "M130.91336,-2.6003754 L144.9668,13.820157 144.63235,155.90351 -45.593261,156.07107 -45.425622,-2.264937 z");
Brush backBrush = GetBackgroundBrush();
Brush borderBrush = GetBorderBrush();
Pen borderPen = new Pen(borderBrush, BorderWidth);
double cornerRadiusCache = CornerRadius;
var geometry = Geometry.Parse(
"M130.91336,-2.6003754 L144.9668,13.820157 144.63235,155.90351 -45.593261,156.07107 -45.425622,-2.264937 z");
drawingContext.PushClip(geometry);
drawingContext.DrawGeometry(borderBrush, borderPen, geometry);
drawingContext.Pop();
//base.OnRender(drawingContext);
}
protected override Size MeasureOverride(Size constraint)
{
UIElement child = this.Child as UIElement;
double borderThickness = BorderWidth*2.0;
if (child != null)
{
Size size = new Size();
bool widthAvail = constraint.Width < borderThickness;
bool heightAvail = constraint.Height < borderThickness;
if (!widthAvail)
{
size.Width = constraint.Width - borderThickness;
}
if (!heightAvail)
{
size.Height = constraint.Height - borderThickness;
}
child.Measure(size);
Size desired = child.DesiredSize;
if (!widthAvail)
{
desired.Width += borderThickness;
}
if (!heightAvail)
{
desired.Height += borderThickness;
}
return desired;
//return base.MeasureOverride(constraint);
}
return new Size(Math.Min(borderThickness, constraint.Width), Math.Min(borderThickness, constraint.Height));
}
protected override Size ArrangeOverride(Size arrangeSize)
{
return base.ArrangeOverride(arrangeSize);
}
private static void OnRenderIsPressedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var border = o as TrapezoidBorder;
if (border != null)
{
if ((bool)e.NewValue == true)
{
}
border.InvalidateVisual();
}
}
// if the mouse is over the control, this fires
private static void OnRenderIsMouseOverChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var border = o as TrapezoidBorder;
if (border != null)
{
border.InvalidateVisual();
}
}
protected virtual Brush GetBackgroundBrush()
{
Brush b = Background;
if (b != null)
{
return b;
}
return Brushes.Orange;
}
protected virtual Brush GetBorderBrush()
{
Brush b = BorderBrush;
if (b != null)
{
return b;
}
return Brushes.DarkGray;
}
}
Here's some XAML where I've tried to use it:
<customControls:TrapezoidBorder
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
Background="Blue"
Margin="45"
BorderWidth="0" BorderBrush="Crimson">
<Grid Style="{StaticResource SubGridContainer}">
<Grid.RowDefinitions>
<RowDefinition Height="7*" />
<RowDefinition Height="4*" />
<RowDefinition Height=".5*" />
<RowDefinition Height="88.5*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" VerticalAlignment="Bottom" Margin="25,0,0,10" Style="{StaticResource LargeHeader}">Customers</TextBlock>
<StackPanel Orientation="Horizontal" Grid.Row="1" Style="{StaticResource SearchBar}">
<TextBlock VerticalAlignment="Bottom">Search</TextBlock>
<TextBox x:Name="SearchTerm"
Template="{StaticResource TextBoxBaseControlTemplate}"
MinWidth="450"
Margin="3"
cal:Message.Attach="[Event KeyDown] = [Action Search($executionContext)]"/>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<RadioButton Name="SortByLastName">Sort by Last Name</RadioButton>
<RadioButton Name="SortByFirstName">Soft by First Name</RadioButton>
</StackPanel>
</StackPanel>
<ListBox x:Name="AccountRegistrationLogs"
ItemTemplate="{StaticResource AccountRegistrationLogDataTemplate}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Background="Beige" Grid.Row="3" FontFamily="Arial" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="CadetBlue">
</SolidColorBrush>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="LightGray">
</SolidColorBrush>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<StaticResource ResourceKey="AccountRegistrationLogPanelStyle"></StaticResource>
</ListBox.ItemContainerStyle>
</ListBox>
<!--<TabControl Grid.Row="3" Grid.Column="0" Margin="5" TabStripPlacement="Top"
Style="{StaticResource TabControlStyle}" FontSize="16">
<TabItem Header="MainTab">
<Border Margin="10">
<TextBlock Text="The quick brown fox jumps over the lazy dog."/>
</Border>
</TabItem>
<TabItem Header="VeryVeryLongTab" />
<TabItem Header="Tab" />
</TabControl>-->
</Grid>
</customControls:TrapezoidBorder>
Thanks