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,
Related
I have a listview with values that are being updated constantly from a different thread.
I want to change the color of the background according to the value of the item.
After reading a lot I came to the following conclusions:
The correct way to set background color for list view item is via style selector.
Style selector is called only once in the initialization of the list.
How can I achieve this simple behavior?
xaml:
<Page
x:Class="MyProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyProject"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.DataRef.Values, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ValWrapper">
<TextBlock Text="{x:Bind Val, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyleSelector>
<local:CustomItemContainerStyleSelector>
<local:CustomItemContainerStyleSelector.Bad>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Red"/>
</Style>
</local:CustomItemContainerStyleSelector.Bad>
<local:CustomItemContainerStyleSelector.Good>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Green"/>
</Style>
</local:CustomItemContainerStyleSelector.CloseToBad>
</local:CustomItemContainerStyleSelector>
</ListView.ItemContainerStyleSelector>
</ListView>
</Grid>
</Page>
cs:
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
}
public class CustomItemContainerStyleSelector : StyleSelector
{
public Style Bad { get; set; }
public Style Good { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
{
double threshold = 1;
ValWrapper v = (ValWrapper)item;
if (v.Val <= threshold)
{
return Bad;
}
else {
return Good;
}
}
}
Whenever the data changes, "NotifyPropertyChanged" is called (implements INotifyPropertyChanged).
Please check the following steps:
Set a temporary variable _tempValue to record previous number.
Bind the Background property to IsUpdate, the initial value is all false.
If the number changes, please set IsUpdate to true, then the Background of ListViewItem turns red.
XAML:
<Page
x:Class="Permisson.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Permisson"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<local:ColorConverter x:Key="ColorConverter"/>
</Page.Resources>
<Grid>
<StackPanel>
<ListView ItemsSource="{x:Bind ViewModel.DataRef, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate >
<DataTemplate x:DataType="local:ValWrapper">
<Grid Background="{Binding IsUpdate, Converter={StaticResource ColorConverter},Mode=OneWay}">
<TextBlock Text="{Binding Val, Mode=OneWay}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="ChangeNum" Click="Button_Click"/>
<Button Content="ChangeNum2" Click="Button_Click_1"/>
</StackPanel>
</Grid>
</Page>
Code behind:
namespace Permisson
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var v = ViewModel.DataRef[0];
v.Val = 9;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var v = ViewModel.DataRef[1];
v.Val = 10;
}
}
public class ViewModel
{
private ObservableCollection<ValWrapper> dataRef = new ObservableCollection<ValWrapper>()
{
new ValWrapper {Val=22,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false },
new ValWrapper {Val=25,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=35,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=45,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false },
new ValWrapper {Val=55,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=65,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false }
};
public ObservableCollection<ValWrapper> DataRef { get { return dataRef; } }
}
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var color = new SolidColorBrush();
if ((bool)value)
{
color.Color = Colors.Red;
}
else
{
color.Color = Colors.Green;
}
return color;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
public class ValWrapper : INotifyPropertyChanged
{
private int val;
private SolidColorBrush brush;
public SolidColorBrush Brush
{
get { return brush; }
set
{
brush = value;
RaisePropertyChanged();
}
}
private int _tempValue;
public int Val
{
get { return val; }
set
{
if(_tempValue != value && _tempValue != 0)
{
IsUpdate = true;
}
val = value;
RaisePropertyChanged();
_tempValue = val;
}
}
private bool _isUpdate;
public bool IsUpdate
{
set
{
_isUpdate = value;
RaisePropertyChanged();
}
get
{
return _isUpdate;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyname));
}
}
}
}
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:
Xaml as below:
<ItemsControl
x:Class="PowersOf2.Windows10.Views.Controls.Board"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PowersOf2.Windows10.Views.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Root" ItemsSource="{Binding Fields, ElementName=Root}" Loaded="Root_Loaded"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid
Width="{Binding FieldWidth, ElementName=Root}"
Height="{Binding FieldHeight, ElementName=Root}"
Loaded="Grid_Loaded" Background="White"
>
<Grid.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Grid.RenderTransform>
<TextBlock Text="{Binding Text}" Foreground="Black"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Fields is IEnumerable of Field which has coordinates X and Y. They are managed by view model. FieldWidth and FieldHeight are dependency properties calculated in code behind.
How to get binding object of nested dependency properties such as TranslateTransform.X and TranslateTransform.Y in code behind?
UPDATE:
Based on this question: Fredrik's answer works as expected until you work with single embedded object in xaml with binding to non-nested properties, but not for nested ones. This issue is more complicated due to ItemsControl containing my Grid.
Code behind below:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Windows.UI.Xaml;
namespace Controls
{
public sealed partial class Board
{
public Board()
{
InitializeComponent();
}
private void Root_Loaded(object sender, RoutedEventArgs e)
{
FieldWidth = 100.0;
FieldHeight = 100.0;
Fields =
new Field[]
{
new Field { X = 100, Y = 100, Text = "one" },
new Field { X = 300, Y = 300, Text = "two" }
};
}
public double FieldWidth
{
get { return (double)GetValue(FieldWidthProperty); }
set { SetValue(FieldWidthProperty, value); }
}
public static readonly DependencyProperty FieldWidthProperty = DependencyProperty.Register(
"FieldWidth", typeof(double), typeof(Board), new PropertyMetadata(0.0)
);
public double FieldHeight
{
get { return (double)GetValue(FieldHeightProperty); }
set { SetValue(FieldHeightProperty, value); }
}
public static readonly DependencyProperty FieldHeightProperty = DependencyProperty.Register(
"FieldHeight", typeof(double), typeof(Board), new PropertyMetadata(0.0)
);
public IEnumerable<Field> Fields
{
get { return (ObservableCollection<Field>)GetValue(FieldsProperty); }
set { SetValue(FieldsProperty, value); }
}
public static readonly DependencyProperty FieldsProperty = DependencyProperty.Register(
"Fields", typeof(IEnumerable<Field>), typeof(Board), new PropertyMetadata(null)
);
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
// here I want to get binding of RenderTransform's properties
}
}
public class Field : INotifyPropertyChanged
{
private int _x;
public int X
{
get { return _x; }
set
{
_x = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("X"));
}
}
private int _y;
public int Y
{
get { return _y; }
set
{
_y = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Y"));
}
}
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Text"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I hope I haven't misinterpreted the question but you can get the transformation and the bound item like this.
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
var grid = (Grid)sender;
//the actual transformation
var render = (Transform)grid.GetValue(RenderTransformProperty);
//the field the transformation is bound to
var field = (Field)grid.DataContext;
//for now this only works in WPF
var binding = BindingOperations.GetBinding(render, TranslateTransform.XProperty);
}
Made an edit for this, but it does not work for winrt.
The method BindingOperations.GetBinding is only available in WPF.
Hope that winrt gets this soon.
I have a user control that contains 2 DoubleUpDown, I have bound point to that controls
<DoubleUpDown x:Name="X" Grid.Column="1" Grid.Row="0" Value="{Binding Path=Value.X, Mode=TwoWay" />
<DoubleUpDown x:Name="Y" Grid.Column="1" Grid.Row="1" Value="{Binding Path=Value.Y, Mode=TwoWay}" />
controls get updated pretty well when I change Value from outside, but Value stays unchanged when I change controls data.
I bound Value to user control from code inside
Point2DEditorView editor = new Point2DEditorView();
Binding binding = new Binding("Value");
binding.Mode = BindingMode.TwoWay;
editor.SetBinding(Point2DEditorView.ValueProperty, binding);
and Point2DEditorView.Value also changed when I insert new coordinates into controls. But that does not affect bound Value.
Point is a value type data. Because of this when you bind it to control boxing and unboxing occurs. For more information see this. So, you may easy solve this problem by creating your own class (not struct!):
class MyPoint
{
public int X { set; get; }
public int Y { set; get; }
}
And then bind this objects to your control and you will see that all works as you expect.
Update
First of all your DoubleUpDown is'n in standart FCL and I think your problem in it. There is a simple example where all works as expect. I created a simple UpDown control for it:
Point class
public class Point2D : INotifyPropertyChanged
{
private double x;
private double y;
public double X
{
set
{
if (value.Equals(x)) return;
x = value;
OnPropertyChanged();
}
get { return x; }
}
public double Y
{
set
{
if (value.Equals(y)) return;
y = value;
OnPropertyChanged();
}
get { return y; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
UpDown xaml
<UserControl x:Name="doubleUpDown" x:Class="PointBind.DoubleUpDown"
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"
mc:Ignorable="d" d:DesignWidth="105" Height="33">
<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=doubleUpDown}">
<TextBox Margin="5,5,0,5" Width="50" Text="{Binding Value}" />
<Button x:Name="Up" x:FieldModifier="private" Margin="5,5,0,5" Content="˄" Width="20" Click="Up_Click" />
<Button x:Name="Down" x:FieldModifier="private" Margin="0,5,0,5" Content="˅" Width="20" Click="Down_Click" />
</StackPanel>
</UserControl>
UpDown .cs
public partial class DoubleUpDown : UserControl
{
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(DoubleUpDown), new PropertyMetadata(0.0));
public DoubleUpDown()
{
InitializeComponent();
DataContext = this;
}
private void Up_Click(object sender, RoutedEventArgs e)
{
Value++;
}
private void Down_Click(object sender, RoutedEventArgs e)
{
Value--;
}
}
Point2DEditorView xaml
<UserControl x:Name="point2DEditorView" x:Class="PointBind.Point2DEditorView"
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"
mc:Ignorable="d"
xmlns:local="clr-namespace:PointBind"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<local:DoubleUpDown Value="{Binding Point.X, ElementName=point2DEditorView, Mode=TwoWay}"/>
<local:DoubleUpDown Value="{Binding Point.Y, ElementName=point2DEditorView, Mode=TwoWay}"/>
</StackPanel>
</UserControl>
UpDown .cs
public partial class Point2DEditorView : UserControl
{
public Point2D Point
{
get { return (Point2D)GetValue(PointProperty); }
set { SetValue(PointProperty, value); }
}
// Using a DependencyProperty as the backing store for Point. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PointProperty =
DependencyProperty.Register("Point", typeof (Point2D), typeof (Point2DEditorView),
new PropertyMetadata(new Point2D {X = 10, Y = 20}));
public Point2DEditorView()
{
InitializeComponent();
}
}
Test form xaml
<Window x:Class="PointBind.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PointBind"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:Point2DEditorView x:Name="pointEditor"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="39,121,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</Window>
And test form .cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
pointEditor.Point = new Point2D{X = 300, Y = 400};
}
}
Hope this helps.
MainWindow.XAML:
<Window x:Class="TestWPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="myMainWindow"
Title="MainWindow" Width="200" Height="250">
<Grid Name="MainGrid" SizeChanged="MainGrid_SizeChanged">
<Grid.LayoutTransform>
<ScaleTransform x:Name="ApplicationScaleTransform"
CenterX="0"
CenterY="0"
ScaleX="{Binding ElementName=myMainWindow, Path=ScaleValue}"
ScaleY="{Binding ElementName=myMainWindow, Path=ScaleValue}" />
</Grid.LayoutTransform>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Height="150">
<TextBlock FontSize="20" Text="Hello World" Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center"/>
<Button Content="Button" VerticalAlignment="Bottom" HorizontalAlignment="Center" Name="myButton"/>
</Grid>
</Grid>
</Window>
Main Window CodeBehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
#region ScaleValue Depdency Property
public static readonly DependencyProperty ScaleValueProperty = DependencyProperty.Register("ScaleValue", typeof(double), typeof(MainWindow), new UIPropertyMetadata(1.0, new PropertyChangedCallback(OnScaleValueChanged), new CoerceValueCallback(OnCoerceScaleValue)));
private static object OnCoerceScaleValue(DependencyObject o, object value)
{
MainWindow mainWindow = o as MainWindow;
if (mainWindow != null)
return mainWindow.OnCoerceScaleValue((double)value);
else
return value;
}
private static void OnScaleValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
MainWindow mainWindow = o as MainWindow;
if (mainWindow != null)
mainWindow.OnScaleValueChanged((double)e.OldValue, (double)e.NewValue);
}
protected virtual double OnCoerceScaleValue(double value)
{
if (double.IsNaN(value))
return 1.0f;
value = Math.Max(0.1, value);
return value;
}
protected virtual void OnScaleValueChanged(double oldValue, double newValue)
{
}
public double ScaleValue
{
get
{
return (double)GetValue(ScaleValueProperty);
}
set
{
SetValue(ScaleValueProperty, value);
}
}
#endregion
private void MainGrid_SizeChanged(object sender, EventArgs e)
{
CalculateScale();
}
private void CalculateScale()
{
double yScale = ActualHeight / 250f;
double xScale = ActualWidth / 200f;
double value = Math.Min(xScale, yScale);
ScaleValue = (double)OnCoerceScaleValue(MainGrid, value);
}
}
I have this sample application. I am applying LayoutTransform on the MainGrid to scale application with the resize of window. How can i avoid my control myButton to apply that transform? I dont want that transform to apply on it.
Two options
1) Don't make it a child, instead only make it visually overlap, using a second grid for example.
2) Apply the inverse of the transformation to the children, this will negate the transformation, can be expensive but should do the trick.
<Button LayoutTransform="{Binding ElementName=MainGrid, Path=LayoutTransform.Inverse}"/>
personally i would use the first option.