Connect a line between two ellipses - wpf

I'm trying to connect a line between two ellipses where if one was dragged the line would move with it. I have a canvas and inside that are two stackpanels... in each stackpanel is an ellipse on the left... content control in the middle... and another ellipse on the right. The idea is to connect a line between the right ellipse from one stackpanel to the left ellipse in the second stackpanel. So far I have this but can't seem to get much farther as the propertypath being used to do the binding doesn't make too much sense to me... which is why I have a Canvas in there right now.
Line line = new Line();
line.Stroke = connectedEllipse.Fill;
line.StrokeThickness = 2;
Binding x1 = new Binding();
Binding x2 = new Binding();
Binding y1 = new Binding();
Binding y2 = new Binding();
x1.Path = new PropertyPath(Canvas.LeftProperty);
x2.Path = new PropertyPath(Canvas.LeftProperty);
y1.Path = new PropertyPath(Canvas.TopProperty);
y2.Path = new PropertyPath(Canvas.TopProperty);
x1.Source = y1.Source = connectedEllipse;
x2.Source = y2.Source = (sender as Ellipse);
line.SetBinding(Line.X1Property, x1);
line.SetBinding(Line.X2Property, x2);
line.SetBinding(Line.Y1Property, y1);
line.SetBinding(Line.Y2Property, y2);

Ok I've hacked up some code which doesn't use the attached properties method. This probably isn't "good" code since I wrote it up in 20 minutes but it will get you started.
MainWindow.xaml
<Window x:Class="EllipseTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:EllipseTest"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" >
<Window.Resources>
<ControlTemplate x:Key="template1">
<Ellipse Width="60" Height="30" Fill="Blue" />
</ControlTemplate>
</Window.Resources>
<Canvas Name="canvas">
<my:ExtendedThumb x:Name="thumb1" Canvas.Left ="0" Canvas.Top="0" DragDelta="myThumb_DragDelta" Template="{StaticResource template1}" />
<my:ExtendedThumb x:Name="thumb2" Canvas.Left ="50" Canvas.Top="20" DragDelta="myThumb_DragDelta" Template="{StaticResource template1}" />
</Canvas>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace EllipseTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Path path;
public MainWindow()
{
InitializeComponent();
}
private void myThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
ExtendedThumb thumb = e.Source as ExtendedThumb;
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + e.HorizontalChange);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + e.VerticalChange);
if (thumb == thumb1)
UpdateLine(thumb, thumb2);
else
UpdateLine(thumb1, thumb);
}
private void UpdateLine(ExtendedThumb firstThumb, ExtendedThumb secondThumb)
{
double left1 = Canvas.GetLeft(firstThumb);
double top1 = Canvas.GetTop(firstThumb);
double left2 = Canvas.GetLeft(secondThumb);
double top2 = Canvas.GetTop(secondThumb);
thumb1.ConnectingLine.StartPoint = new Point(left1 +firstThumb.ActualWidth / 2, top1 + firstThumb.ActualHeight / 2);
thumb1.ConnectingLine.EndPoint = new Point(left2 + secondThumb.ActualWidth / 2, top2 + secondThumb.ActualHeight / 2);
thumb2.ConnectingLine.StartPoint = new Point(left2 + secondThumb.ActualWidth / 2, top2 + secondThumb.ActualHeight / 2);
thumb2.ConnectingLine.EndPoint = new Point(left1 + firstThumb.ActualWidth / 2, top1 + firstThumb.ActualHeight / 2);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
path = new Path();
path.Stroke = Brushes.Black;
path.StrokeThickness = 2;
canvas.Children.Add(path);
LineGeometry line = new LineGeometry();
path.Data = line;
thumb1.ConnectingLine = line;
thumb2.ConnectingLine = line;
UpdateLine(thumb1, thumb2);
}
}
}
ExtendedThumb.cs
using System;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Linq;
using System.Text;
namespace EllipseTest
{
public class ExtendedThumb : Thumb
{
public LineGeometry ConnectingLine { get; set; }
public ExtendedThumb() : base() { ConnectingLine = new LineGeometry(); }
}
}
Also, I got the idea from the contents of this link: http://denisvuyka.wordpress.com/2007/10/13/wpf-draggable-objects-and-simple-shape-connectors/

Related

Is it possible to change caret width for TextBox in silverlight when overwrite mode?

I have the TextBox in silverlight 4 in 2 modes: insert and overwrite.
Can anyone help me? If I press the overwrite mode, I want to make the caret blinking size bigger.
I have used to CaretBrush, but it can change the color of caret only.
Many thanks if you have suggestion or the sample code.
At the time looking someone can help, I tried this solution for myself, It's abit complex, but I hope someone can use my code and customize yourself. Someone can get this code for trying if you have this case
The MainPage.xaml code:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.micros`enter code here`oft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid Name="LayoutRoot">
<Canvas x:Name="canvasDataView" VerticalAlignment="Top" Background="White" HorizontalAlignment="Left">
<TextBox HorizontalAlignment="Left" Canvas.Left="50" VerticalAlignment="Top" x:Name="positionTextBox" Canvas.ZIndex="2" Text="position" Canvas.Top="25" Visibility="Collapsed" Width="100" Height="25"></TextBox>
<TextBox x:Name="textBox1" Text="asdfasdfasd asda asdf as adfasdfads adf asdf adfasdf" SelectionForeground="AliceBlue" KeyDown="textBox1_KeyDown" KeyUp="textBox1_KeyUp" TextWrapping="NoWrap" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible" AcceptsReturn="True" Height="25" Width="500" />
<TextBox x:Name="shadowTextBox" Canvas.ZIndex="-1" Canvas.Left="350" Height="20" Width="20" HorizontalScrollBarVisibility="Visible" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" Visibility="Visible" TextWrapping="NoWrap"/>
</Canvas>
</Grid>
</UserControl>
The code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Expression.Interactivity;
using System.Windows.Controls.Primitives;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
Duration duration = new Duration(TimeSpan.FromSeconds(0.5));
Storyboard storybroad = new Storyboard();
Rectangle myRectangle = null;
ScrollViewer sv;
public MainPage()
{
InitializeComponent();
textBox1.KeyUp += new KeyEventHandler(textBox1_KeyUp);
textBox1.TextChanged += new TextChangedEventHandler(textBox1_TextChanged);
textBox1.CaretBrush = new SolidColorBrush(Colors.Transparent);
}
void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
shadowTextBox.Text = textBox1.Text;
shadowTextBox.SelectionStart = textBox1.SelectionStart;
Point p = getCaretPosition(textBox1, shadowTextBox);
MoveCaret(p);
}
void textBox1_KeyDown(object sender, KeyEventArgs e)
{
shadowTextBox.Text = textBox1.Text;
shadowTextBox.SelectionStart = textBox1.SelectionStart;
}
void textBox1_KeyUp(object sender, KeyEventArgs e)
{
shadowTextBox.Text = textBox1.Text;
shadowTextBox.SelectionStart = textBox1.SelectionStart;
// get caret position
Point p = getCaretPosition(textBox1,shadowTextBox);
MoveCaret(p);
}
private Point getCaretPosition(TextBox textBox, TextBox shadowTextBox)
{
Point _point = new Point();
// get main textbox's scroll offset, if any
getScrollBar(textBox);
double initVerticalOffset = sv.VerticalOffset;
double initHorizontalOffset = sv.HorizontalOffset;
// get shadow box scroll offset
getScrollBar(shadowTextBox);
double vOffset = sv.VerticalOffset;
double hOffset = sv.HorizontalOffset;
// caret position is scroll offset of shadaw minus scroll offset of main (if any)
_point.Y = vOffset - initVerticalOffset;
_point.X = hOffset - initHorizontalOffset;
return _point;
}
private void getScrollBar(UIElement src)
{
// walk visual tree for this object until we get the scrollviewer
if (src.GetType().ToString() == "System.Windows.Controls.ScrollViewer")
sv = src as ScrollViewer;
else
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(src); i++)
{
UIElement elem = (UIElement)VisualTreeHelper.GetChild(src, i);
getScrollBar(elem);
}
}
}
void MoveCaret(Point point)
{
if (storybroad == null)
storybroad = new Storyboard();
storybroad.Stop();
if (point != new Point(0, 0))
{
if (myRectangle != null && canvasDataView.Children.Contains(myRectangle))
{
canvasDataView.Children.Remove(myRectangle);
}
myRectangle = new Rectangle();
myRectangle.SetValue(Canvas.TopProperty, point.Y);
myRectangle.SetValue(Canvas.LeftProperty, point.X);
myRectangle.Width = 5;
myRectangle.Height = 16;
myRectangle.Fill = new SolidColorBrush();
storybroad.BeginTime = new TimeSpan(0, 0, 0, 0, 0);
storybroad.RepeatBehavior = RepeatBehavior.Forever;
ColorAnimation color = new ColorAnimation();
canvasDataView.Children.Add(myRectangle);
color.From = Colors.Transparent;
color.To = Colors.Green;
color.Duration = duration;
storybroad.Children.Add(color);
Storyboard.SetTarget(color, myRectangle);
Storyboard.SetTargetProperty(color, new PropertyPath("(Fill).(SolidColorBrush.Color)"));
storybroad.Begin();
}
}
}
}

Scroll multiple image in wpf

<Grid>
<Grid.Background>
<ImageBrush ImageSource="Images\Desert.jpg"
Stretch="UniformToFill" TileMode="Tile"
ViewportUnits="Absolute" Viewport="0,0,1024,768"/>
</Grid.Background>
<Grid.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<RectAnimation Storyboard.TargetProperty="Background.Viewport"
To="-1024,0,1024,768" Duration="0:0:10"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
</Grid>
I have this code that scroll a single image in a grid with looping.
Now I have 2 images 1(red) and 2(yellow) am looking something like this.
and it will scroll in loop
You can build a single ImageSource based on multiple images if you wanted to follow your current approach. I have 2 png's (Desert1.png and Desert2.png in an Images folder) and use DataBinding to set the ImageBrush ImageSource to a property defined on the code behind:
<!- Your original xaml ... only difference is the binding -->
<ImageBrush ImageSource="{Binding CombinedImage}"
Stretch="None" TileMode="Tile"
ViewportUnits="Absolute" Viewport="0,0,1024,768"/>
Here's a sample of the code behind (feel free to refactor / use / abuse as you see fit):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xaml;
namespace WpfApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var uriSource1 = new Uri(#"pack://application:,,,/Images/Desert1.png", UriKind.Absolute);
BitmapImage bitmapImage1 = new BitmapImage(uriSource1);
var uriSource2 = new Uri(#"pack://application:,,,/Images/Desert2.png", UriKind.Absolute);
BitmapImage bitmapImage2 = new BitmapImage(uriSource2);
this.DataContext = this;
List<BitmapImage> images = new List<BitmapImage>() { bitmapImage1, bitmapImage2 };
CombinedImage = GetCombinedImage(images);
}
private static RenderTargetBitmap GetCombinedImage(IEnumerable<BitmapImage> images )
{
// Get total width of all images
int totalWidthOfAllImages = images.Sum(p => (int)p.Width);
// Get max height of all images
int maxHeightOfAllImages = images.Max(p => (int)p.Height);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
double left = 0;
foreach (BitmapImage image in images)
{
drawingContext.DrawImage(image, new Rect(left, 0, image.Width, image.Height));
left += image.Width;
}
}
RenderTargetBitmap bmp = new RenderTargetBitmap(totalWidthOfAllImages, maxHeightOfAllImages, 96, 96, PixelFormats.Default);
bmp.Render(drawingVisual);
return bmp;
}
public ImageSource CombinedImage { get; private set; }
}
}
I have code for image slider. I created using user control for windows phone
Please check with this video http://www.screencast.com/t/XnhHwQFY For first time you need to change logic.
But, I think same code you can use for WPF also
ImageSlider.xaml - Create user control
<UserControl x:Class="ImageSliderDemo.ImageSlider"
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"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480">
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<Canvas Height="220" x:Name="imageSliderCanvas" Width="451">
<Image x:Name="imageViewOne"
Canvas.Left="0"
Canvas.Top="0"
Height="220" Width="440" Canvas.ZIndex="9">
<Image.RenderTransform>
<TranslateTransform />
</Image.RenderTransform>
</Image>
<Image x:Name="imageViewTwo"
Canvas.Left="0"
Height="220" Width="440" Canvas.ZIndex="10">
<Image.RenderTransform>
<TranslateTransform />
</Image.RenderTransform>
</Image>
</Canvas>
<StackPanel x:Name="PART_Host" />
</Grid>
</UserControl>
ImageSlider.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Threading;
using System.Windows.Media.Imaging;
using System.Windows.Markup;
namespace ImageSliderDemo
{
public partial class ImageSlider : UserControl
{
private const int LOWER_ZINDEX = 9, UPPER_ZINDEX = 11, POSITION_FROM480 = 480, POSITION_TO0 = 0;
private int nextImage = 1;
#region "Image Slider Properies"
#region "Property - Length Readonly"
public static readonly DependencyProperty LengthProperty = DependencyProperty.Register("Length", typeof(int), typeof(ImageSlider), new PropertyMetadata(0));
public int Length
{
get { return (int)GetValue(LengthProperty); }
private set { SetValue(LengthProperty, value); }
}
#endregion
#region "Property - Begin Delay Readonly"
public static readonly DependencyProperty BeginDelayProperty = DependencyProperty.Register("BeginDelay", typeof(double), typeof(ImageSlider), new PropertyMetadata(5000.00));
public double BeginDelay
{
get { return (double)GetValue(BeginDelayProperty); }
set { SetValue(BeginDelayProperty, value); }
}
#endregion
#region "Property - Animation Duration Readonly"
public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register("AnimationDuration", typeof(double), typeof(ImageSlider), new PropertyMetadata(900.00));
public double AnimationDuration
{
get { return (double)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
#endregion
#region "Property - Images"
public static readonly DependencyProperty ImagesProperty = DependencyProperty.Register("Images", typeof(List<SliderImage>), typeof(ImageSlider), new PropertyMetadata(null));
public List<SliderImage> Images
{
get { return (List<SliderImage>)GetValue(ImagesProperty); }
set { SetValue(ImagesProperty, value); }
}
#endregion
#endregion
public ImageSlider()
{
InitializeComponent();
}
/// <summary>
/// This Start method used begin the animation
/// </summary>
public void Start()
{
if (this.Images != null)
{
this.Length = this.Images.Count;
hidePrevious(imageViewOne);
showNext(imageViewTwo);
}
else
{
MessageBox.Show("Please add atleast two images");
}
}
#region "Animation methods"
private void showNext(Image imageView)
{
TranslateTransform trans = imageView.RenderTransform as TranslateTransform;
DoubleAnimation animation = new DoubleAnimation();
animation.To = POSITION_TO0;
animation.Duration = TimeSpan.FromMilliseconds(this.AnimationDuration);
animation.From = POSITION_FROM480;
animation.BeginTime = TimeSpan.FromMilliseconds(this.BeginDelay);
Storyboard.SetTarget(animation, trans);
Storyboard.SetTargetProperty(animation, new
PropertyPath(TranslateTransform.XProperty));
// Create storyboard, add animation, and fire it up!
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(animation);
storyboard.Begin();
Canvas.SetZIndex(imageView, UPPER_ZINDEX);
imageView.Visibility = Visibility.Visible;
if (nextImage > this.Length)
{
nextImage = 1;
}
BitmapImage nextBitmapImage = new BitmapImage(new Uri(this.Images[nextImage-1].Path, UriKind.Relative));
imageView.Source = nextBitmapImage;
nextImage++;
}
private void hidePrevious(Image imageView)
{
TranslateTransform trans = imageView.RenderTransform as TranslateTransform;
DoubleAnimation animation = new DoubleAnimation();
animation.To = - POSITION_FROM480;
animation.Duration = TimeSpan.FromMilliseconds(this.AnimationDuration);
animation.From = POSITION_TO0;
animation.BeginTime = TimeSpan.FromMilliseconds(this.BeginDelay);
Storyboard.SetTarget(animation, trans);
Storyboard.SetTargetProperty(animation, new
PropertyPath(TranslateTransform.XProperty));
// Create storyboard, add animation, and fire it up!
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(animation);
storyboard.Begin();
animation.Completed += hideAnimation_Completed;
}
private void hideAnimation_Completed(object sender, EventArgs e)
{
if (Canvas.GetZIndex(imageViewOne) > Canvas.GetZIndex(imageViewTwo))
{
Canvas.SetZIndex(imageViewOne, LOWER_ZINDEX);
hidePrevious(imageViewOne);
showNext(imageViewTwo);
}
else
{
Canvas.SetZIndex(imageViewTwo, LOWER_ZINDEX);
hidePrevious(imageViewTwo);
showNext(imageViewOne);
}
}
#endregion
}
}
Ctrl + B , Just build once
SliderImage.cs -- Add new class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ImageSliderDemo
{
public class SliderImage
{
public string Name { get; set; }
public string Path { get; set; }
public SliderImage(string name, string path)
{
this.Name = name;
this.Path = path;
}
}
}
then do this steps
MainPage.xaml
add at top of xaml page xmlns:local="clr-namespace:[YOUR_PROJECT_NAMESPACE]"
then add just add this below in xaml
<local:ImageSlider x:Name="imageSlider"/>
MainPage.xaml.cs load images
List<SliderImage> images = new List<SliderImage>();
images.Add(new SliderImage("One", "Images/1.png"));
images.Add(new SliderImage("Two", "Images/2.png"));
images.Add(new SliderImage("Three", "Images/3.png"));
images.Add(new SliderImage("Four", "Images/4.png"));
imageSlider.Images = images;
imageSlider.Start();
Note : I used ImageSliderDemo as my namespace. If your using different please make sure you updated in user control as well. And I found only first time it is show same image twice. you can change the logic in ImageSlider.xaml.cs file

Dragging a WPF Slider thumb with a busy UI thread results in unexpected values

EDIT: There was a lock taken for a long time in the Slider.ValueChanged event. Removing this stops the weirdness. Bad practice, but the behavior still doesn't make sense to me. It probably has to do with the message queue, but I'd like an explanation if possible.
When there is significant work on the UI thread*, the WPF Slider does not interact with the mouse as expected. If I drag the thumb in one direction, the thumb will often advance in front of the mouse cursor, and then go back. The movement should result in only increasing ValueChanged events, but the events sometimes decrease. Sometimes this oscillation happens behind the mouse cursor, too. It seems that the mouse's position is predicted by its current velocity.
Can I change this behavior, or am I doing something wrong?
Toggling IsSnapToTickEnabled doesn't help.
*Assigning a large BitmapSource to an Image. The BitmapSource is created in a worker thread.
<Window x:Class="WPFSliderBug.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">
<Grid>
<Image Name="image1" Stretch="Fill" />
<Slider HorizontalAlignment="Right" Name="slider1" VerticalAlignment="Stretch" Orientation="Vertical" />
</Grid>
</Window>
xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Threading;
namespace WPFSliderBug
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private volatile bool m_run = true;
private double m_sliderValue;
public MainWindow()
{
InitializeComponent();
this.Closing += new System.ComponentModel.CancelEventHandler(MainWindow_Closing);
this.slider1.Minimum = 1;
this.slider1.Maximum = 30;
this.slider1.SmallChange = 0.5;
this.slider1.LargeChange = 0.5;
this.slider1.TickFrequency = 0.5;
this.slider1.TickPlacement = System.Windows.Controls.Primitives.TickPlacement.Both;
this.slider1.ValueChanged += new RoutedPropertyChangedEventHandler<double>(slider1_ValueChanged);
Thread t = new Thread((unused_state) =>
{
while (m_run)
{
BitmapSource bmp;
lock (this)
{
bmp = ToBitmapSource();
bmp.Freeze();
}
this.Dispatcher.Invoke(new Action(delegate()
{
image1.Source = bmp;
}));
}
});
t.Start();
}
void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
lock (this)
{
m_sliderValue = e.NewValue;
}
}
void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
m_run = false;
}
public BitmapSource ToBitmapSource()
{
Random rng = new Random();
double dpi = 96;
int bytesPerPixel = 1;
int width = 2048;
int height = 2048;
int stride = ((width * 32 + 31) & ~31) / 8;
UInt32[] pixelData = new UInt32[width * height];
for (int i = 0; i < pixelData.Length; ++i)
{
double r = rng.NextDouble();
r = Math.Sin(r) * Math.Cos(r) + Math.Asin(r);
pixelData[i] = (uint)(r * UInt32.MaxValue);
}
BitmapSource bmpSource = BitmapSource.Create(width, height, dpi, dpi,
PixelFormats.Bgr32, null, pixelData, stride);
return bmpSource;
}
}
}

WPF Multi-touch tracking touch points

I'm trying to do a simple application that, when a user touchs a screen, app creates simple point, ellipse, or sth 2d object, and when user moves his finger it should follow, but also when there is a scond touch at the same time new object also has to be created and do the same thing with respect to users movement. Whenever user fingersup, object will be deleted.
To do this, I'm trying to change the touchdrawing code from this link http://www.cookingwithxaml.com/recipes/wpf4/wpf4touch.zip but I couldn't figure out which method should I need to change ?
Can you give advice about that please ?
Thanks.
Here is some sample xaml/C# code that does what I think you want:
MainWindow.xaml:
<Window x:Class="MultitouchExperiments.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">
<Grid>
<Canvas
x:Name="TouchCanvas"
TouchDown="TouchCanvas_TouchDown" TouchUp="TouchCanvas_TouchUp"
TouchMove="TouchCanvas_TouchMove" TouchLeave="TouchCanvas_TouchLeave"
TouchEnter="TouchCanvas_TouchEnter"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Background="Black"
IsManipulationEnabled="True" />
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
namespace MultitouchExperiments
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Dictionary<TouchDevice, Ellipse> _Followers = new Dictionary<TouchDevice, Ellipse>();
public MainWindow()
{
InitializeComponent();
}
private void TouchCanvas_TouchDown(object sender, TouchEventArgs e)
{
TouchCanvas.CaptureTouch(e.TouchDevice);
Ellipse follower = new Ellipse();
follower.Width = follower.Height = 50;
follower.Fill = Brushes.White;
follower.Stroke = Brushes.White;
TouchPoint point = e.GetTouchPoint(TouchCanvas);
follower.RenderTransform = new TranslateTransform(point.Position.X, point.Position.Y);
_Followers[e.TouchDevice] = follower;
TouchCanvas.Children.Add(follower);
}
private void TouchCanvas_TouchUp(object sender, TouchEventArgs e)
{
TouchCanvas.ReleaseTouchCapture(e.TouchDevice);
TouchCanvas.Children.Remove(_Followers[e.TouchDevice]);
_Followers.Remove(e.TouchDevice);
}
private void TouchCanvas_TouchMove(object sender, TouchEventArgs e)
{
if (e.TouchDevice.Captured == TouchCanvas)
{
Ellipse follower = _Followers[e.TouchDevice];
TranslateTransform transform = follower.RenderTransform as TranslateTransform;
TouchPoint point = e.GetTouchPoint(TouchCanvas);
transform.X = point.Position.X;
transform.Y = point.Position.Y;
}
}
private void TouchCanvas_TouchLeave(object sender, TouchEventArgs e)
{
//Debug.WriteLine("leave " + e.TouchDevice.Id);
}
private void TouchCanvas_TouchEnter(object sender, TouchEventArgs e)
{
//Debug.WriteLine("enter " + e.TouchDevice.Id);
}
}
}

Translate a WPF canvas at high scale factors isn't smooth away from origin

Imagine you have a canvas that you want to scale to a very high value and then allow "panning."
A good example is a geographic tool that needs to allow panning with "zoom" levels from the extent of the whole earth down to the extent of a few meters.
I've found that if you are scaled in to more than, say 500,000, translating becomes very erratic, but ONLY if you are viewing far from the canvas's 0,0 origin!
I've tried to translate using the RenderTransform of the canvas AND I've tried it by literally moving anothercanvas on top of the scaled canvas. I've also seen the same issue in someone else's sample app online.
The following example code provides for panning (click and drag) at two different zoom locations. If you implement the code, you can hit one button to zoom in to 0,0 where you will find nice, smooth mouse panning. Then use the other button to zoom in to 200, 200 and smooth panning is no more!
Any idea why this is or how one could fix it?
XAML for sample:
<Window x:Class="TestPanZoom.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="500" Width="500" Loaded="Window_Loaded">
<Grid PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown" MouseMove="Grid_MouseMove">
<Canvas Name="canvas1"></Canvas>
<Button Height="31"
Name="button1"
Click="button1_Click"
Margin="12,12,0,0"
VerticalAlignment="Top"
HorizontalAlignment="Left" Width="270">
Zoom WAY in to 0,0 and get smooth panning
</Button>
<Button Height="31"
Name="button2"
Click="button2_Click"
Margin="12,49,0,0"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Width="270">
Zoom WAY in to 200, 200 -- NO smooth panning
</Button>
</Grid>
</Window>
Code for sample:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TestPanZoom
{
/// <summary>
/// Interaction logic for Window1.xaml
/// Demo of an issue with translate transform when scale is very high
/// but ONLY when you are far from the canvas's 0,0 origin.
/// Why? Is their a fix?
/// </summary>
public partial class Window1 : Window
{
Point m_clickPoint;
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Add a 2x2 black ellipse centered at 0,0
Ellipse el = new Ellipse();
el.Fill = Brushes.Black;
el.Width = 2;
el.Height = 2;
el.HorizontalAlignment = HorizontalAlignment.Left;
el.VerticalAlignment = VerticalAlignment.Top;
el.Margin = new Thickness(0 - el.Width / 2, 0 - el.Height / 2, 0, 0);
canvas1.Children.Add(el);
// Add a 1x1 red rectangle with its top/left corner at 0,0
Rectangle r = new Rectangle();
r.Fill = Brushes.Red;
r.Width = 1;
r.Height = 1;
r.HorizontalAlignment = HorizontalAlignment.Left;
r.VerticalAlignment = VerticalAlignment.Top;
r.Margin = new Thickness(0, 0, 0, 0);
canvas1.Children.Add(r);
// Add a 2x2 purple ellipse at a point 200,200
Point otherPoint = new Point(200, 200);
el = new Ellipse();
el.Fill = Brushes.Purple;
el.Width = 2;
el.Height = 2;
el.HorizontalAlignment = HorizontalAlignment.Left;
el.VerticalAlignment = VerticalAlignment.Top;
el.Margin = new Thickness(otherPoint.X - el.Width / 2, otherPoint.Y - el.Height / 2, 0, 0);
canvas1.Children.Add(el);
// Add a 1x1 blue rectangle with its top/left corner at 200,200
r = new Rectangle();
r.Fill = Brushes.Blue;
r.Width = 1;
r.Height = 1;
r.HorizontalAlignment = HorizontalAlignment.Left;
r.VerticalAlignment = VerticalAlignment.Top;
r.Margin = new Thickness(otherPoint.X, otherPoint.Y, 0, 0);
canvas1.Children.Add(r);
}
private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
m_clickPoint = e.GetPosition(this);
}
// Pan with the mouse when left-mouse is down
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Point mousePosition = e.GetPosition(this);
double xDiff = mousePosition.X - m_clickPoint.X;
double yDiff = mousePosition.Y - m_clickPoint.Y;
TranslateTransform tt = new TranslateTransform(xDiff, yDiff);
TransformGroup tg = new TransformGroup();
tg.Children.Add(canvas1.RenderTransform);
tg.Children.Add(tt);
canvas1.RenderTransform = tg;
m_clickPoint = e.GetPosition(this);
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
TransformGroup tg = new TransformGroup();
double scale = 1000000;
double xCenter = 0;
double yCenter = 0;
double xOffset = (canvas1.ActualHeight / 2.0 - xCenter);
double yOffset = (canvas1.ActualWidth / 2.0 - yCenter);
ScaleTransform st = new ScaleTransform(scale, scale);
st.CenterX = xCenter;
st.CenterY = yCenter;
TranslateTransform tt = new TranslateTransform(xOffset, yOffset);
tg.Children.Add(st);
tg.Children.Add(tt);
canvas1.RenderTransform = tg;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
TransformGroup tg = new TransformGroup();
double scale = 1000000;
double xCenter = 200;
double yCenter = 200;
double xOffset = (canvas1.ActualHeight / 2.0 - xCenter);
double yOffset = (canvas1.ActualWidth / 2.0 - yCenter);
ScaleTransform st = new ScaleTransform(scale, scale);
st.CenterX = xCenter;
st.CenterY = yCenter;
TranslateTransform tt = new TranslateTransform(xOffset, yOffset);
tg.Children.Add(st);
tg.Children.Add(tt);
canvas1.RenderTransform = tg;
}
}
}
This is caused by Canvas itself. It is not well perfomed for more advanced rendering. Insted you need to use a Visual class. It's a bit harder yet you gain the advantage of low level rendering.
Solution download
Here is the code: MainWindow.xaml
<Window x:Class="VisualTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="VisualLayer" Height="350.4" Width="496.8"
xmlns:local="clr-namespace:VisualTest"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<Button Name="button1" Click="button1_Click" Margin="5" Padding="5,0">
Zoom WAY in to 0,0
</Button>
<Button Name="button2" Click="button2_Click" Margin="5" Padding="5,0">
Zoom WAY in to 200, 200
</Button>
<Button Name="button3" Click="button3_Click" Margin="5" Padding="5,0">
Zoom back
</Button>
</StackPanel>
<local:DrawingCanvas Grid.Column="1" x:Name="drawingSurface" Background="White" ClipToBounds="True"
MouseLeftButtonDown="drawingSurface_MouseLeftButtonDown"
MouseLeftButtonUp="drawingSurface_MouseLeftButtonUp"
MouseMove="drawingSurface_MouseMove">
</local:DrawingCanvas>
</Grid>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace VisualTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// Variables for dragging shapes.
private bool isDragging = false;
private Vector clickOffset;
private DrawingVisual selectedVisual;
// Drawing constants.
private Brush drawingBrush = Brushes.Black;
private Brush selectedDrawingBrush = Brushes.LightGoldenrodYellow;
private Pen drawingPen = new Pen(Brushes.SteelBlue, 3);
private Size squareSize = new Size(10, 10);
public MainWindow()
{
InitializeComponent();
DrawingVisual v = new DrawingVisual();
DrawSquare(v, new Point(0, 0));
drawingSurface.AddVisual(v);
v = new DrawingVisual();
DrawSquare(v, new Point(200, 200));
drawingSurface.AddVisual(v);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
TransformGroup tg = new TransformGroup();
double scale = 1000000;
double xCenter = 0;
double yCenter = 0;
double xOffset = (drawingSurface.ActualHeight / 2.0 - xCenter);
double yOffset = (drawingSurface.ActualWidth / 2.0 - yCenter);
ScaleTransform st = new ScaleTransform(scale, scale);
st.CenterX = xCenter;
st.CenterY = yCenter;
TranslateTransform tt = new TranslateTransform(xOffset, yOffset);
tg.Children.Add(st);
tg.Children.Add(tt);
drawingSurface.RenderTransform = st;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
TransformGroup tg = new TransformGroup();
double scale = 1000000;
double xCenter = 200;
double yCenter = 200;
double xOffset = (drawingSurface.ActualHeight / 2.0 - xCenter);
double yOffset = (drawingSurface.ActualWidth / 2.0 - yCenter);
ScaleTransform st = new ScaleTransform(scale, scale);
st.CenterX = xCenter;
st.CenterY = yCenter;
TranslateTransform tt = new TranslateTransform(xOffset, yOffset);
tg.Children.Add(st);
tg.Children.Add(tt);
drawingSurface.RenderTransform = st;
}
private void button3_Click(object sender, RoutedEventArgs e)
{
ScaleTransform st = new ScaleTransform(1, 1);
drawingSurface.RenderTransform = st;
}
private void drawingSurface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point pointClicked = e.GetPosition(drawingSurface);
DrawingVisual visual = drawingSurface.GetVisual(pointClicked);
if (visual != null)
{
// Calculate the top-left corner of the square.
// This is done by looking at the current bounds and
// removing half the border (pen thickness).
// An alternate solution would be to store the top-left
// point of every visual in a collection in the
// DrawingCanvas, and provide this point when hit testing.
Point topLeftCorner = new Point(
visual.ContentBounds.TopLeft.X ,
visual.ContentBounds.TopLeft.Y );
DrawSquare(visual, topLeftCorner);
clickOffset = topLeftCorner - pointClicked;
isDragging = true;
if (selectedVisual != null && selectedVisual != visual)
{
// The selection has changed. Clear the previous selection.
ClearSelection();
}
selectedVisual = visual;
}
}
// Rendering the square.
private void DrawSquare(DrawingVisual visual, Point topLeftCorner)
{
using (DrawingContext dc = visual.RenderOpen())
{
Brush brush = drawingBrush;
dc.DrawRectangle(brush, null,
new Rect(topLeftCorner, squareSize));
}
}
private void drawingSurface_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
isDragging = false;
}
private void ClearSelection()
{
Point topLeftCorner = new Point(
selectedVisual.ContentBounds.TopLeft.X ,
selectedVisual.ContentBounds.TopLeft.Y );
DrawSquare(selectedVisual, topLeftCorner);
selectedVisual = null;
}
private void drawingSurface_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
Point pointDragged = e.GetPosition(drawingSurface) + clickOffset;
DrawSquare(selectedVisual, pointDragged);
}
}
}
}
DrawingCanvas.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows;
namespace VisualTest
{
public class DrawingCanvas : Panel
{
private List<Visual> visuals = new List<Visual>();
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return visuals.Count;
}
}
public void AddVisual(Visual visual)
{
visuals.Add(visual);
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
public void DeleteVisual(Visual visual)
{
visuals.Remove(visual);
base.RemoveVisualChild(visual);
base.RemoveLogicalChild(visual);
}
public DrawingVisual GetVisual(Point point)
{
HitTestResult hitResult = VisualTreeHelper.HitTest(this, point);
return hitResult.VisualHit as DrawingVisual;
}
private List<DrawingVisual> hits = new List<DrawingVisual>();
public List<DrawingVisual> GetVisuals(Geometry region)
{
hits.Clear();
GeometryHitTestParameters parameters = new GeometryHitTestParameters(region);
HitTestResultCallback callback = new HitTestResultCallback(this.HitTestCallback);
VisualTreeHelper.HitTest(this, null, callback, parameters);
return hits;
}
private HitTestResultBehavior HitTestCallback(HitTestResult result)
{
GeometryHitTestResult geometryResult = (GeometryHitTestResult)result;
DrawingVisual visual = result.VisualHit as DrawingVisual;
if (visual != null &&
geometryResult.IntersectionDetail == IntersectionDetail.FullyInside)
{
hits.Add(visual);
}
return HitTestResultBehavior.Continue;
}
}
}
Dragging system need to be rewrite. The idea is simple yet implementation is a bit complicated.

Resources