WPF C# Updating Polyline points realtime from different UIElement - c#

Hello I'm creating polyline with dots(elipse) which will be updated by user mouse move after he clicked on dot, problem is that dot is moving which is good, but does not update source which is ObservableCollection
MainWindow.xaml
<Window x:Class="sampleWPF.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:l="clr-namespace:sampleWPF" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" Background="Green" MouseRightButtonDown="MainWindow_OnMouseRightButtonDown_">
<Window.Resources>
<l:PointCollectionConverter x:Key="pointCollectionConverter" />
</Window.Resources>
<Grid>
<Canvas x:Name="Canvas" Background="Yellow" Width="300" Height="300" MouseMove="UIElement_OnMouseMove">
<Polyline x:Name="polyline" Stroke="Black" Width="300" Height="300" Points="{Binding PointCollection, Converter={StaticResource pointCollectionConverter}}" />
<ItemsControl ItemsSource="{Binding PointCollection}">
<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}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Blue" MouseLeftButtonDown="UIElement_OnMouseLeftButtonDown" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Grid>
Some explanation:
MainWindow_OnMouseRightButtonDown_ stops selected elipse move.
UIElement_OnMouseMove moves elipse in canvas
UIElement_OnMouseLeftButtonDown selects elipse which will be moved
MainWindow.xaml.cs
public partial class MainWindow: INotifyPropertyChanged {
public MainWindow() {
InitializeComponent();
DataContext = this;
PointCollection.Add(new Point(0, 0));
PointCollection.Add(new Point(20, 20));
PointCollection.Add(new Point(30, 30));
PointCollection.Add(new Point(50, 50));
PointCollection.Add(new Point(80, 80));
PointCollection.Add(new Point(100, 100));
}
private ObservableCollection < Point > PointCollectionProperty {
get;
set;
} = new ObservableCollection < Point > ();
public ObservableCollection < Point > PointCollection {
get => PointCollectionProperty;
set {
PointCollectionProperty = value;
OnPropertyChanged(nameof(PointCollection));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string caller) {
PropertyChanged ? .Invoke(this, new PropertyChangedEventArgs(caller));
}
Point point;
private Point selectedPoint;
bool activated = false;
private Ellipse selectedEllipse;
private ContentPresenter selectedContentPresenter;
private void UIElement_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
if (sender is Ellipse es) {
Mouse.Capture(es);
selectedEllipse = es;
var parent = VisualTreeHelper.GetParent(selectedEllipse);
if (parent != null) {
selectedContentPresenter = parent as ContentPresenter;
}
point = Mouse.GetPosition(selectedContentPresenter);
if (PointCollection.Any(t => t.X == Canvas.GetLeft(selectedContentPresenter) && t.Y == Canvas.GetTop(selectedContentPresenter))) {
Debug.WriteLine("found");
selectedPoint = PointCollection.FirstOrDefault(t =>
t.X == Canvas.GetLeft(selectedContentPresenter) && t.Y == Canvas.GetTop(selectedContentPresenter));
} else {
selectedPoint = new Point();
Debug.WriteLine("not found");
}
activated = true;
}
}
private void UIElement_OnMouseMove(object sender, MouseEventArgs e) {
if (selectedContentPresenter != null) {
if (activated) {
double top = Canvas.GetTop(selectedContentPresenter) + Mouse.GetPosition(selectedContentPresenter).Y + point.Y;
double left = Canvas.GetLeft(selectedContentPresenter) + Mouse.GetPosition(selectedContentPresenter).X + point.X;
Canvas.SetTop(selectedContentPresenter, top);
Canvas.SetLeft(selectedContentPresenter, left);
selectedPoint.X = left;
selectedPoint.Y = top;
OnPropertyChanged(nameof(PointCollection));
}
}
}
private void MainWindow_OnMouseRightButtonDown_(object sender, MouseButtonEventArgs e) {
foreach(Point child in PointCollection) {
Debug.WriteLine(child.X + " " + child.Y);// shows original values from public MainWindow()
}
Mouse.Capture(null);
activated = false;
selectedEllipse = null;
selectedContentPresenter = null;
}
}
public class PointCollectionConverter: IValueConverter {
Debug.WriteLine("Called");
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value.GetType() == typeof(ObservableCollection < Point > ) && targetType == typeof(PointCollection)) {
var pointCollection = new PointCollection();
foreach(var point in value as ObservableCollection < Point > ) pointCollection.Add(point);
return pointCollection;
}
return null;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return null;
}
}
What I'm missing here since elipse does move but polyline stays untouched
Edit: added Debug.WriteLine to show if its working, but spams debug but collection stays still the same
Image proving that it breaks after mouse move

Related

How to get mouse position when using XamlBehaviorsWpf and MVVM (Formerly Interactivity)

Now that XamlBehaviorsWpf [Tag missing, but I don't have the rights to add] has replaced System.Windows.Interactivity in Blend SDK which is deprecated I'm struggling to figure out how to use it to get Mouse position on a canvas.
MouseX and MouseY below I think is the old Interactivity Syntax but I can't seem to find a XamlBehaviorsWpf equivalent.
As such, using these properties creates a compile error.
The Click method is working and returns the coordinates at their default 999,999 value.
Does anyone know how I can get the current mouse position using XamlBehaviorsWpf?
XAML:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Height="800" Background="Black">
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="MouseLeftButtonDown">
<behaviors:CallMethodAction TargetObject="{Binding}" MethodName="Click"/>
</behaviors:EventTrigger>
<behaviors:EventTrigger EventName="MouseMove">
<behaviors:ChangePropertyAction MouseX="{Binding X, Mode=OneWayToSource}"/>
<behaviors:ChangePropertyAction MouseY="{Binding Y, Mode=OneWayToSource}"/>
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
ViewModel
public void Click(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(X.ToString() + ","+ Y.ToString());
}
private double x = 999;
public double X
{
get { return x; }
set { SetAndNotify(ref this.x, value); }
}
private double y = 999;
public double Y
{
get { return y; }
set { SetAndNotify(ref this.y, value); }
}
I lot of my issues seemed to stem from a typo, which been corrected in the question (thanks Andy).
There's an easy to follow article here that I used to get it working.
You can basically swap all System.Windows.Interactivity references with XamlBehaviorsWpf and it will work all the same.
My working code below for easy reference or if the link above dissapears:
XAML
<UserControl x:Class="Drain.Views.Cad.CadView"
x:Name="cad"
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:behaviors="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Drain.Views.Cad"
xmlns:vm="clr-namespace:Drain.ViewModels.Cad"
xmlns:cad="clr-namespace:Drain.Models.Cad"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<behaviors:Interaction.Behaviors>
<behaviors:FluidMoveBehavior AppliesTo="Children" />
</behaviors:Interaction.Behaviors>
<ItemsControl x:Name="MainPanel">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Height="800" Background="Black">
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="MouseLeftButtonDown">
<behaviors:CallMethodAction TargetObject="{Binding}" MethodName="Click"/>
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<behaviors:Interaction.Behaviors>
<vm:MouseBehaviour MouseX="{Binding X, Mode=OneWayToSource}"
MouseY="{Binding Y, Mode=OneWayToSource}" />
</behaviors:Interaction.Behaviors>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=OneWay}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=OneWay}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</UserControl>
VIEWMODEL
public void Click(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(X.ToString() + ","+ Y.ToString());
}
private double x = 999;
public double X
{
get { return x; }
set { SetAndNotify(ref this.x, value); }
}
private double y = 999;
public double Y
{
get { return y; }
set { SetAndNotify(ref this.y, value); }
}
MouseBehaviour Class
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Xaml.Behaviors;
namespace Drain.ViewModels.Cad
{
public class MouseBehaviour : Behavior<Panel>
{
public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
"MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
"MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public double MouseY
{
get { return (double)GetValue(MouseYProperty); }
set { SetValue(MouseYProperty, value); }
}
public double MouseX
{
get { return (double)GetValue(MouseXProperty); }
set { SetValue(MouseXProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
}
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var pos = mouseEventArgs.GetPosition(AssociatedObject);
MouseX = pos.X;
MouseY = pos.Y;
}
protected override void OnDetaching()
{
AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
}
}
}

Change background color of list view item in uwp whenever the value changes

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));
}
}
}
}

Getting binding of transform property from code behind

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,

Moving a grid to where I click, with animation

I'm trying to make a collection of tabs move to where ever I click on a canvas. I managed to get it working with fixed values, but cannot get it to work with a left mouse button click.
Here's my code:
public partial class MainWindow : Window
{
User Player = new User();
ThicknessConverter perimeter = new ThicknessConverter();
Inventory Inventory = new Inventory();
BreadCrumb Crumb = new BreadCrumb();
Locations Locations = new Locations();
PointAnimation myPointAnimation = new PointAnimation();
ThicknessAnimation myThicknessAnimation = new ThicknessAnimation();
DoubleAnimation da = new DoubleAnimation();
Point p = new Point();
private void Hansel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
da.From = Canvas.GetLeft(Hansel);
da.From = Canvas.GetTop(Hansel);
p.X = Mouse.GetPosition(PlayArea);
da.To = p.X; //Convert.ToDouble(PointToScreen(Mouse.GetPosition(this.)));
da.Duration = new Duration(TimeSpan.FromSeconds(2));
Hansel.BeginAnimation(Canvas.LeftProperty, da);
}
}
So, how would I go about getting the mouse position and converting those points to double coordinates?
<Window x:Class="MiscSamples.ClickToMove"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ClickToMove" Height="300" Width="300">
<ItemsControl ItemsSource="{Binding}" PreviewMouseDown="ItemsControl_PreviewMouseDown"
Background="#05FFFFFF">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Stroke="Black" StrokeThickness="2" Fill="Blue"
Height="20" Width="20"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Code Behind:
public partial class ClickToMove : Window
{
public List<MovableObject> Objects { get; set; }
public ClickToMove()
{
InitializeComponent();
Objects = new List<MovableObject>
{
new MovableObject() {X = 100, Y = 100}
};
DataContext = Objects;
}
private void ItemsControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var position = e.GetPosition(this);
Objects.First().MoveToPosition(position.X, position.Y);
}
}
ViewModel:
public class MovableObject: INotifyPropertyChanged
{
private double _x;
public double X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged("X");
}
}
private double _y;
public double Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged("Y");
}
}
private System.Threading.Timer MoveTimer;
private double DestinationX;
private double DestinationY;
public void MoveToPosition(double x, double y)
{
DestinationX = x;
DestinationY = y;
if (MoveTimer != null)
MoveTimer.Dispose();
MoveTimer = new Timer(o => MoveStep(), null, 0, 10);
}
private void MoveStep()
{
if (Math.Abs(X - DestinationX) > 5)
{
if (X < DestinationX)
X = X+5;
else if (X > DestinationX)
X = X-5;
}
if (Math.Abs(Y - DestinationY) > 5)
{
if (Y < DestinationY)
Y = Y + 5;
else if (Y > DestinationY)
Y = Y - 5;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}

How do you animate a line on a canvas in C#?

How would you make a line slowly draw across the screen?
I am trying to animate a line on a canvas in a C#/WPF project.
I would like to use C# code and not XAML.
I Have a running sample that uses the MVVM Pattern and creates Lines within a ListBox that has a Canvas as its ItemsPanel.
I actually made it for this question, but the OP kind of dissapeared and never contacted me about it.
This is what it looks like in my computer:
The main part of it is this:
<ListBox ItemsSource="{Binding}" x:Name="lst" Height="500" Width="500">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style TargetType="Control">
<Setter Property="Opacity" Value="0"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Line X1="{Binding X1}" Y1="{Binding Y1}"
X2="{Binding X2}" Y2="{Binding Y2}"
StrokeThickness="{Binding Thickness}"
Opacity="{Binding Opacity}"
x:Name="Line">
<Line.Stroke>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="{Binding Color1}" Offset="0"/>
<GradientStop Color="{Binding Color2}" Offset="1"/>
</LinearGradientBrush>
</Line.Stroke>
</Line>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect" TargetName="Line">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ViewModel:
public class LineViewModel : INotifyPropertyChanged
{
#region Timer-based Animation
private System.Threading.Timer Timer;
private static Random Rnd = new Random();
private bool _animate;
public bool Animate
{
get { return _animate; }
set
{
_animate = value;
NotifyPropertyChanged("Animate");
if (value)
StartTimer();
else
StopTimer();
}
}
private int _animationSpeed = 1;
public int AnimationSpeed
{
get { return _animationSpeed; }
set
{
_animationSpeed = value;
NotifyPropertyChanged("AnimationSpeed");
if (Timer != null)
Timer.Change(0, 100/value);
}
}
private static readonly List<int> _animationSpeeds = new List<int>{1,2,3,4,5};
public List<int> AnimationSpeeds
{
get { return _animationSpeeds; }
}
public void StartTimer()
{
StopTimer();
Timer = new Timer(x => Timer_Tick(), null, 0, 100/AnimationSpeed);
}
public void StopTimer()
{
if (Timer != null)
{
Timer.Dispose();
Timer = null;
}
}
private void Timer_Tick()
{
X1 = X1 + Rnd.Next(-2, 3);
Y1 = Y1 + Rnd.Next(-2, 3);
X2 = X2 + Rnd.Next(-2, 3);
Y2 = Y2 + Rnd.Next(-2, 3);
}
#endregion
#region Coordinates
private double _x1;
public double X1
{
get { return _x1; }
set
{
_x1 = value;
NotifyPropertyChanged("X1");
}
}
private double _y1;
public double Y1
{
get { return _y1; }
set
{
_y1 = value;
NotifyPropertyChanged("Y1");
}
}
private double _x2;
public double X2
{
get { return _x2; }
set
{
_x2 = value;
NotifyPropertyChanged("X2");
}
}
private double _y2;
public double Y2
{
get { return _y2; }
set
{
_y2 = value;
NotifyPropertyChanged("Y2");
}
}
#endregion
#region Other Properties
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private double _thickness;
public double Thickness
{
get { return _thickness; }
set
{
_thickness = value;
NotifyPropertyChanged("Thickness");
}
}
public Color Color1 { get; set; }
public Color Color2 { get; set; }
private double _opacity = 1;
public double Opacity
{
get { return _opacity; }
set
{
_opacity = value;
NotifyPropertyChanged("Opacity");
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}));
}
#endregion
}
Edit: Source code now on GitHub
You will need to use a Storyboard and animate the Line.X2 and Line.Y2 Properties. See if this works for you.
MainWindow.xaml
<Window x:Class="WpfApplication1.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">
<Canvas Name="myCanvas">
<Button Canvas.Left="248" Canvas.Top="222" Content="Button" Height="23" Name="button1" Width="75" Click="button1_Click" />
</Canvas>
</Window>
Button Click Event
private void button1_Click(object sender, RoutedEventArgs e)
{
Line line = new Line();
myCanvas.Children.Add(line);
line.Stroke = Brushes.Red;
line.StrokeThickness = 2;
line.X1 = 0;
line.Y1 = 0;
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation(line.Y2 , 100, new Duration(new TimeSpan(0, 0, 1)));
DoubleAnimation da1 = new DoubleAnimation(line.X2, 100, new Duration(new TimeSpan(0, 0, 1)));
Storyboard.SetTargetProperty(da, new PropertyPath("(Line.Y2)"));
Storyboard.SetTargetProperty(da1, new PropertyPath("(Line.X2)"));
sb.Children.Add(da);
sb.Children.Add(da1);
line.BeginStoryboard(sb);
}

Categories