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));
}));
}
}
Related
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
I have been trying to set up a two-way binding in wpf. There is a canvas that is populated with ContentControls, each one containing a filled rectangle. Through a thumb, each ContentControl can be made larger and therefore have a changed width.
These ContentControls have been generated by code and live within a class (CanvasElement), which is used for other calculations.
I'd like to set up a two way binding between the ContentControl Property Width and the public double variable Width within the CanvasElement class. When the Thumb is used to change the width of the contentControl, the Width of the CanvasElement is updated, but the other way it doesn't work.
Here is what I have so far:
public class CanvasElement
{
private double width;
public double height;
private Point location; // This is the upper left point of the rectangle
public Brush color;
public string UID;
public ContentControl canvasElement;
public CanvasElement(Point location, double width, double height, Brush color, string UID)
{
this.location = location;
this.width = width;
this.height = height;
this.color = color;
this.UID = UID;
canvasElement = new ContentControl() { Width = this.width, Height = this.height, Uid = UID };
Canvas.SetLeft(canvasElement, this.location.X);
Canvas.SetTop(canvasElement, this.location.Y);
canvasElement.Content = new Rectangle() {
IsHitTestVisible = false,
Fill = this.color,
Stroke =Brushes.LightGray,
StrokeThickness = 2,
Margin = new Thickness(0,5,0,5),
RadiusX = 10,
RadiusY = 10};
addBinding();
}
private void addBinding()
{
Binding widthBinding = new Binding();
widthBinding.Source = this;
widthBinding.Path = new PropertyPath("Width");
widthBinding.Mode = BindingMode.TwoWay;
widthBinding.NotifyOnSourceUpdated = true;
widthBinding.NotifyOnTargetUpdated = true;
//widthBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(canvasElement, ContentControl.WidthProperty, widthBinding);
}
public double Width
{
get
{
return width;
}
set
{
if(width != value)
{
width = value;
OnPropertyChanged();
}
}
}
As well as:
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I am thankful for every hint I can get!
Thanks for helping out!
As Clemens pointed out in his comment the ItemsControl is the right way to do this. As I have different UIElements that are added to the canvas I needed to add an ItemsControl.ItemTemplateSelector as well as an ItemsControlItemContainerStyleSelector.
XAML
<AdornerDecorator ClipToBounds="True">
<ItemsControl ItemsSource="{Binding CanvasElementList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="FloralWhite"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyleSelector>
<local:CustomStyleSelector>
<local:CustomStyleSelector.CanvasStyle_TL>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_TL>
<local:CustomStyleSelector.CanvasStyle_TR>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Right" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_TR>
<local:CustomStyleSelector.CanvasStyle_BL>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Bottom" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_BL>
<local:CustomStyleSelector.CanvasStyle_BR>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Right" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Bottom" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_BR>
<local:CustomStyleSelector.LineStyle>
<Style TargetType="ContentPresenter">
</Style>
</local:CustomStyleSelector.LineStyle>
</local:CustomStyleSelector>
</ItemsControl.ItemContainerStyleSelector>
<ItemsControl.ItemTemplateSelector>
<local:CustomTemplateSelectors>
<local:CustomTemplateSelectors.LabelTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Text}"
FontWeight="{Binding FontWeight}"
FontSize="{Binding FontSize}"/>
</DataTemplate>
</local:CustomTemplateSelectors.LabelTemplate>
<local:CustomTemplateSelectors.LineTemplate>
<DataTemplate>
<Line X1="{Binding X1}"
X2="{Binding X2}"
Y1="{Binding Y1}"
Y2="{Binding Y2}"
Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"
StrokeDashArray="{Binding StrokeDashArray}"/>
</DataTemplate>
</local:CustomTemplateSelectors.LineTemplate>
<local:CustomTemplateSelectors.CanvasElementTemplate>
<DataTemplate>
<ContentControl Width="{Binding Path=Width, Mode=TwoWay}" Height="{Binding Path=Height, Mode=TwoWay}"
Style="{StaticResource ResourceKey=DesignerItemStyle}"
MouseDoubleClick="ContentControl_MouseDoubleClick">
<Rectangle Fill="{Binding Color}"
Stroke="LightGray"
StrokeThickness="2"
Margin="0,5,0,5"
RadiusX="10"
RadiusY="10"
IsHitTestVisible="False"/>
</ContentControl>
</DataTemplate>
</local:CustomTemplateSelectors.CanvasElementTemplate>
</local:CustomTemplateSelectors>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
</AdornerDecorator>
Code
In the corresponding .cs file there are these ObservableCollections and the CompositeCollection. The later is the element that binds to the ItemsControl. To add new elements you have to add elements to the Observable Collections
CanvasElementList4Canvas = new ObservableCollection<CanvasElement>();
LineList4Canvas = new ObservableCollection<CustomLine>();
LabelList4Canvas = new ObservableCollection<LabelTextBlock>();
CanvasElementList = new CompositeCollection();
CanvasElementList.Add(new CollectionContainer() { Collection = CanvasElementList4Canvas });
CanvasElementList.Add(new CollectionContainer() { Collection = LineList4Canvas });
CanvasElementList.Add(new CollectionContainer() { Collection = LabelList4Canvas });
To set up the binding the CustomLine Class is shown here. The CanvasElement and the LabelTextBlock class are set up in the same way.
CustomLine
public class CustomLine : INotifyPropertyChanged
{
private double _X1;
private double _X2;
private double _Y1;
private double _Y2;
private int _strokeThickness = 3;
private Brush _stroke = Brushes.Black;
private DoubleCollection _strokeDashArray = new DoubleCollection() { 1.0, 0.0 };
public double X1 { get { return _X1; } set { if (_X1 != value) { _X1 = value; NotifyPropertyChanged("X1"); } } }
public double X2 { get { return _X2; } set { if (_X2 != value) { _X2 = value; NotifyPropertyChanged("X2"); } } }
public double Y1 { get { return _Y1; } set { if (_Y1 != value) { _Y1 = value; NotifyPropertyChanged("Y1"); } } }
public double Y2 { get { return _Y2; } set { if (_Y2 != value) { _Y2 = value; NotifyPropertyChanged("Y2"); } } }
public int StrokeThickness { get { return _strokeThickness; } set { if (_strokeThickness != value) { _strokeThickness = value; NotifyPropertyChanged("StrokeThickness"); } } }
public Brush Stroke { get { return _stroke; } set { if (_stroke != value) { _stroke = value; NotifyPropertyChanged("Stroke"); } } }
public DoubleCollection StrokeDashArray { get { return _strokeDashArray; } set { if (_strokeDashArray != value) { _strokeDashArray = value; NotifyPropertyChanged("StrokeDashArray"); } } }
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and finally the custom selectors are needed to use the right template and the right style for within the canvas:
public class CustomTemplateSelectors : DataTemplateSelector
{
public DataTemplate CanvasElementTemplate { get; set; }
public DataTemplate LineTemplate { get; set; }
public DataTemplate LabelTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is CanvasElement)
return CanvasElementTemplate;
else if (item is CustomLine)
return LineTemplate;
else if (item is LabelTextBlock)
return LabelTemplate;
else return base.SelectTemplate(item, container);
}
}
public class CustomStyleSelector : StyleSelector
{
public Style CanvasStyle_TL { get; set; }
public Style CanvasStyle_TR { get; set; }
public Style CanvasStyle_BL { get; set; }
public Style CanvasStyle_BR { get; set; }
public Style LineStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is CanvasElement)
return CanvasStyle_TL;
else if (item is CustomLine)
return LineStyle;
else if (item is LabelTextBlock)
{
var tempItem = item as LabelTextBlock;
if (tempItem.Tag == "TL")
return CanvasStyle_TL;
else if (tempItem.Tag == "TR")
return CanvasStyle_TR;
else if (tempItem.Tag == "BL")
return CanvasStyle_BL;
else if (tempItem.Tag == "BR")
return CanvasStyle_BR;
else return base.SelectStyle(item, container);
}
else return base.SelectStyle(item, container);
}
}
I'm trying to conform to the MVVM structure when getting the mouse position when it is over an image in a wpf application. The mouse position should be converted to a pixel location with respect to the image.
I have this working when Image_MouseMove is in ImagePositionView.xaml.cs, but I'm at a bit of a loss (even after trying to read other threads) as to how to achieve this using the MVVM structure.
I have added a reference to MVVMLight in hopes that this will make this task easier, but I have never used to before..
This is what I have so far:
View:
I have added these references based on what I have seen:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
<UserControl x:Class="ImagePixelLocation.View.ImagePositionView"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
xmlns:local="clr-namespace:ImagePixelLocation"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1000" Background="White">
<Grid>
<Viewbox HorizontalAlignment="Center">
<Grid Name="ColorImage">
<Image x:Name="ImageOnDisplay" Source="{Binding ColourImage}" Stretch="UniformToFill" />
</Grid>
</Viewbox>
</Grid>
</UserControl>
ViewModel:
ViewModelBase exposes INofityPropertyChanged and IDisposable
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GalaSoft.MvvmLight;
namespace ImagePixelView.ViewModel
{
class ImagePositionViewModel : ViewModelBase
{
private WriteableBitmap colourBitmap = null;
public ImageSource ColourImage
{
get
{
return this.colourBitmap;
}
}
public ManualSelectionViewModel()
{
// Open image to writeablebitmap
string path = #"C:\Some\Path\To\ColorImage.png";
Stream imageStreamSource = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource source = decoder.Frames[0];
int width = source.PixelWidth;
int height = source.PixelHeight;
int stride = source.Format.BitsPerPixel / 8 * width;
byte[] data = new byte[stride * height];
source.CopyPixels(data, stride, 0);
this.colourBitmap = new WriteableBitmap(width, height, 96.0, 96.0, source.Format, null);
this.colourBitmap.WritePixels(new Int32Rect(0, 0, width, height), data, stride, 0);
}
private void Image_MouseMove(object sender, MouseEventArgs e)
{
BitmapSource bitmapImage = (BitmapSource)this.ColourImage;
string xCoord = (e.GetPosition(ImageOnDisplay).X * bitmapImage.PixelWidth / ImageOnDisplay.ActualWidth).ToString();
string yCoord = (e.GetPosition(ImageOnDisplay).Y * bitmapImage.PixelHeight / ImageOnDisplay.ActualHeight).ToString();
System.Diagnostics.Debug.WriteLine("mouse location is X:" + xCoord + ", Y:" + yCoord);
}
}
}
I guess the main thing is how do I get access to the view element ImageOnDisplay from within ImagePositionViewModel.
I do this with a behaviour. First I declare an interface that my view model will implement:
public interface IMouseCaptureProxy
{
event EventHandler Capture;
event EventHandler Release;
void OnMouseDown(object sender, MouseCaptureArgs e);
void OnMouseMove(object sender, MouseCaptureArgs e);
void OnMouseUp(object sender, MouseCaptureArgs e);
}
public class MouseCaptureArgs
{
public double X {get; set;}
public double Y { get; set; }
public bool LeftButton { get; set; }
public bool RightButton { get; set; }
}
And here's a behaviour that uses it:
public class MouseCaptureBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty ProxyProperty = DependencyProperty.RegisterAttached(
"Proxy",
typeof(IMouseCaptureProxy),
typeof(MouseCaptureBehavior),
new PropertyMetadata(null, OnProxyChanged));
public static void SetProxy(DependencyObject source, IMouseCaptureProxy value)
{
source.SetValue(ProxyProperty, value);
}
public static IMouseCaptureProxy GetProxy(DependencyObject source)
{
return (IMouseCaptureProxy)source.GetValue(ProxyProperty);
}
private static void OnProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is IMouseCaptureProxy)
{
(e.OldValue as IMouseCaptureProxy).Capture -= OnCapture;
(e.OldValue as IMouseCaptureProxy).Release -= OnRelease;
}
if (e.NewValue is IMouseCaptureProxy)
{
(e.NewValue as IMouseCaptureProxy).Capture += OnCapture;
(e.NewValue as IMouseCaptureProxy).Release += OnRelease;
}
}
static void OnCapture(object sender, EventArgs e)
{
var behavior = sender as MouseCaptureBehavior;
if (behavior != null)
behavior.AssociatedObject.CaptureMouse();
}
static void OnRelease(object sender, EventArgs e)
{
var behavior = sender as MouseCaptureBehavior;
if (behavior != null)
behavior.AssociatedObject.ReleaseMouseCapture();
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreviewMouseDown += OnMouseDown;
this.AssociatedObject.PreviewMouseMove += OnMouseMove;
this.AssociatedObject.PreviewMouseUp += OnMouseUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.PreviewMouseDown -= OnMouseDown;
this.AssociatedObject.PreviewMouseMove -= OnMouseMove;
this.AssociatedObject.PreviewMouseUp -= OnMouseUp;
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs {
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseDown(this, args);
}
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs {
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseMove(this, args);
}
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs
{
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseUp(this, args);
}
}
}
To use this behaviour you add it to the target UI element and bind to an object that implements the proxy interface. In this case I made the MainViewModel implement the interface so I just bind to that:
<!-- Canvas must have a background, even if it's Transparent -->
<Canvas Background="White" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Behaviors>
<behaviors:MouseCaptureBehavior Proxy="{Binding}" />
</i:Interaction.Behaviors>
The view model now needs to provide mouse handlers which the behaviour will call, it also needs to provide the Capture/Release events which the behaviour will respond to when raised by the view model:
public class MainViewModel : ViewModelBase, IMouseCaptureProxy
{
public event EventHandler Capture;
public event EventHandler Release;
public void OnMouseDown(object sender, MouseCaptureArgs e) {...}
public void OnMouseMove(object sender, MouseCaptureArgs e) {...}
public void OnMouseUp(object sender, MouseCaptureArgs e) {...}
}
UPDATE: It should be self-evident, but just in case not: the sender that you pass in to the Capture and Release events should be the same one you received via the MouseDown/Move/Up handlers. The event args passed to Capture/Receive aren't used and can be null.
Solution for Loading image and display its xy coordinates with rgb values when mouse move on image using MVVM..
Hope this will be help full for some one..
ViewModel:
class MainWindowViewModel : ViewModelBase
{
private Bitmap Img;
public ICommand OpenImg { get; set; }
public MainWindowViewModel()
{
OpenImg = new RelayCommand(openImg, (obj) => true);
}
private void openImg(object obj = null)
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png;*.bmp;*.tiff|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
ImgPath = op.FileName;
Img = new Bitmap(ImgPath);
}
}
private string _ImgPath;
public string ImgPath
{
get
{
return _ImgPath;
}
set
{
_ImgPath = value;
OnPropertyChanged("ImgPath");
}
}
private ICommand _mouseMoveCommand;
public ICommand MouseMoveCommand
{
get
{
if (_mouseMoveCommand == null)
{
_mouseMoveCommand = new RelayCommand(param => ExecuteMouseMove((MouseEventArgs)param));
}
return _mouseMoveCommand;
}
set { _mouseMoveCommand = value; }
}
private void ExecuteMouseMove(MouseEventArgs e)
{
System.Windows.Point p = e.GetPosition(((IInputElement)e.Source));
XY = String.Format("X: {0} Y:{1}", (int)p.X, (int)p.Y);
BitmapData bd = Img.LockBits(new Rectangle(0, 0, Img.Width, Img.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* ptr = (byte*)bd.Scan0;
int x = (int)p.X * 3;
int y = (int)p.Y * bd.Stride;
RGB = "R: "+ptr[x + y + 2].ToString() + " G: " + ptr[x + y + 1].ToString() + " B: " + ptr[x + y].ToString();
}
Img.UnlockBits(bd);
}
private string xy;
public string XY
{
get { return xy; }
set
{
xy = value;
OnPropertyChanged("XY");
}
}
private string rgb;
public string RGB
{
get { return rgb; }
set
{
rgb = value;
OnPropertyChanged("RGB");
}
}
}
MainWindow.xaml
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainWindowViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainWindowViewModel}">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*" />
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Menu FontSize="20">
<MenuItem Header="File">
<MenuItem Header="Open" Command="{Binding OpenImg}"/>
</MenuItem>
</Menu>
</Grid>
<Grid Grid.Row="1" Background="LightGray">
<Viewbox Margin="3,3,3,3">
<Image x:Name="img" Stretch="None" Source="{Binding ImgPath}"
Model:MouseBehaviour.MouseMoveCommand="{Binding MouseMoveCommand}">
</Image>
</Viewbox>
</Grid>
<Grid Grid.Row="2">
<StackPanel Orientation="Horizontal">
<TextBox Focusable="False" Text="{Binding XY}" Width="100"/>
<TextBox Focusable="False" Text="{Binding RGB}" Width="115"/>
</StackPanel>
</Grid>
</Grid>
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,
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);
}