WPF Canvas children rect box - c#

Is there any way to create a copy of canvas with the new canvas will be as a box of children elements of the first canvas ?
first canvas
second canvas
i want something like that i am showing in the second canvas.

You can do this by deriving from Canvas and overriding OnVisualChildrenChanged.
You must make changes public so that your copied Canvas (let's call it the target Canvas) can be properly updated.
What you want to do is fairly complex if you need to listen to changes for when the source canvas has children whose properties are updated. In this case, you must again notify your target canvas of changes, which can be done by adding bindings.
XAML
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
Title="MainWindow" Height="437.042" Width="525">
<Grid>
<Border Height="213" Margin="10,10,10,0" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="1">
<local:ObservableCanvas x:Name="CanvasSource"
VisualChildrenChanged="CanvasSource_VisualChildrenChanged">
<!-- Add some elements -->
<Ellipse Width="10" Height="10" Fill="Red"/>
<Ellipse Canvas.Left="10" Canvas.Top="10"
Width="20" Height="20" Fill="Green"/>
<Ellipse Canvas.Left="30" Canvas.Top="30"
Width="30" Height="30" Fill="Blue"/>
<Ellipse Canvas.Left="60" Canvas.Top="60"
Width="40" Height="40" Fill="Yellow"/>
</local:ObservableCanvas>
</Border>
<Border Margin="148,228,148,10" BorderBrush="Black" BorderThickness="1">
<Canvas x:Name="CanvasTarget" Loaded="CanvasTarget_Loaded"/>
</Border>
</Grid>
</Window>
At design time, this is the window:
In particular, notice the VisualChildrenChanged="CanvasSource_VisualChildrenChanged". This is how the changes are made public. We handle these changes in the code-behind for the MainWindow.
ObservableCanvas
//This class notifies listeners when child elements are added/removed & changed.
public class ObservableCanvas : Canvas, INotifyPropertyChanged
{
//Implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
//This function should be called when a child element has a property updated.
protected virtual void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
//Create a routed event to indicate when the ObservableCanvas has changes to its child collection.
public static readonly RoutedEvent VisualChildrenChangedEvent = EventManager.RegisterRoutedEvent(
"VisualChildrenChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ObservableCanvas));
//Create CLR event handler.
public event RoutedEventHandler VisualChildrenChanged
{
add { AddHandler(VisualChildrenChangedEvent, value); }
remove { RemoveHandler(VisualChildrenChangedEvent, value); }
}
//This function should be called to notify listeners
//to changes to the child collection.
protected virtual void RaiseVisualChildrenChanged()
{
RaiseEvent(new RoutedEventArgs(VisualChildrenChangedEvent));
}
//Override to make the changes public.
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
//Create bindings here to properties you need to monitor for changes.
//This example shows how to listen for changes to the Fill property.
//You may need to add more bindings depending on your needs.
Binding binding = new Binding("Fill");
binding.Source = visualAdded;
binding.NotifyOnTargetUpdated = true;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
SetBinding(Shape.FillProperty, binding);
//Instruct binding target updates to cause a global property
//update for the ObservableCanvas.
//This will make child property changes visible to the outside world.
Binding.AddTargetUpdatedHandler(this,
new EventHandler<DataTransferEventArgs>((object sender, DataTransferEventArgs e) =>
{
RaisePropertyChanged("Fill");
}));
//Notify listeners that the ObservableCanvas had an item added/removed.
RaiseVisualChildrenChanged();
}
}
As seen above, this class primarily handles the adding/removing of child elements to/from the ObservableCanvas. You may need to keep a global list of bindings for the elements added, and you may need to consider destroying bindings when elements are removed.
Code-Behind
Lastly, we need to handle the publicly made notifications so that the target Canvas can be updated. For simplicity, we do this in the MainWindow's code-behind.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Demonstrate that children added from inside the code-behind are reflected.
Rectangle newRectangle = new Rectangle() { Width = 50, Height = 50, Fill = Brushes.Orange };
CanvasSource.Children.Add(newRectangle);
Canvas.SetLeft(newRectangle, 100);
Canvas.SetTop(newRectangle, 100);
CanvasSource.PropertyChanged += CanvasSource_PropertyChanged;
//Also, demonstrate that child property changes can be seen.
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(2) };
timer.Tick += (sender, args) =>
{
timer.Stop();
newRectangle.Fill = Brushes.Brown;
};
timer.Start();
}
//This event handler is called when a child is added to or removed from
//the ObservableCanvas.
private void CanvasSource_VisualChildrenChanged(object sender, RoutedEventArgs e)
{
ObservableCanvas source = sender as ObservableCanvas;
if (source == null) return;
CopyElements(source);
}
//This event handler is called when a child element of the ObservableCanvas
//has a property that changes.
void CanvasSource_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
ObservableCanvas source = sender as ObservableCanvas;
if (source == null) return;
CopyElements(source);
}
//This event handler is used to ensure that the target Canvas
//has the elements copied from the source when initialized.
private void CanvasTarget_Loaded(object sender, RoutedEventArgs e)
{
CopyElements(CanvasSource);
}
//This function creates a brand new copy of the ObservableCanvas's
//children and puts it into the target Canvas.
private void CopyElements(ObservableCanvas source)
{
if (CanvasTarget == null) return;
CanvasTarget.Children.Clear(); //Start from scratch.
foreach (UIElement child in source.Children)
{
//We need to create a deep clone of the elements to they copy.
//This is necessary since we can't add the same child to two different
//UIlements.
UIElement clone = (UIElement)XamlReader.Parse(XamlWriter.Save(child));
CanvasTarget.Children.Add(clone);
}
}
}
Final Results
Here is the window upon startup. It has a copy of the elements in the target Canvas.
And since we have a property change occur 2 seconds after initialization (see the code-behind), here is the window after the change occurs:

I have an answer with lines as children, but i want a better solution.
Point firstPoint = new Point(DrawCanvas.ActualWidth, DrawCanvas.ActualHeight);
Point endPoint = new Point(0, 0);
foreach (Line element in DrawCanvas.Children)
{
double x = element.X1;
double y = element.Y1;
if (x < firstPoint.X)
firstPoint.X = x;
if (y < firstPoint.Y)
firstPoint.Y = y;
if (element.X2 > endPoint.X)
endPoint.X = element.X2;
if (element.Y2 > endPoint.Y)
endPoint.Y = element.Y2;
}
double offsetX = firstPoint.X - 5;
double offsetY = firstPoint.Y - 5;
var children = DrawCanvas.Children.Cast<Line>().ToArray();
DrawCanvas.Children.Clear();
Rectangle rect = new Rectangle();
rect.Stroke = new SolidColorBrush(Color.FromRgb(0, 111, 0));
rect.Fill = new SolidColorBrush(Color.FromRgb(0, 111, 111));
rect.Width = endPoint.X - offsetX + 5;
rect.Height = endPoint.Y - offsetY + 5;
Canvas.SetLeft(rect, 0);
Canvas.SetTop(rect, 0);
newCanvas.Children.Add(rect);
newCanvas.Width = rect.Width;
newCanvas.Height = rect.Height;
foreach (Line element in children)
{
Line newLine = element;
newLine.X1 = element.X1 - offsetX;
newLine.X2 = element.X2 - offsetX;
newLine.Y1 = element.Y1 - offsetY;
newLine.Y2 = element.Y2 - offsetY;
newCanvas.Children.Add(newLine);
}

Related

Different MouseMove behaviour with ItemsControl?

I have WPF form, and in XAML I have label which on holding MouseDown I can move around form and on MouseRelease leave it in new position.
This is working as expected.
XAML code:
<Canvas>
<Label Content="Label"
Background="ForestGreen"
Padding="12,7"
Canvas.Left="{Binding XPosition}"
Canvas.Top="{Binding YPosition}"
MouseDown="Label_MouseDown"
MouseUp="Label_MouseUp"
MouseMove="Label_MouseMove"/>
</Canvas>
C#
public partial class frmTables : Window, INotifyPropertyChanged
{
private Point BasePoint = new Point(0.0, 0.0);
private double DeltaX = 0.0;
private double DeltaY = 0.0;
private bool moving = false;
private Point PositionInLabel;
public frmTables()
{
InitializeComponent();
this.DataContext = this;
}
public double XPosition
{
get { return BasePoint.X + DeltaX; }
}
public double YPosition
{
get { return BasePoint.Y + DeltaY; }
}
private void Label_MouseDown(object sender, MouseButtonEventArgs e)
{
Label l = e.Source as Label;
if (l != null)
{
l.CaptureMouse();
moving = true;
PositionInLabel = e.GetPosition(l);
lblCoord.Content = "MouseDown";
}
}
private void Label_MouseMove(object sender, MouseEventArgs e)
{
if (moving)
{
Point p = e.GetPosition(null);
DeltaX = p.X - BasePoint.X - PositionInLabel.X;
DeltaY = p.Y - BasePoint.Y - PositionInLabel.Y;
RaisePropertyChanged("XPosition");
RaisePropertyChanged("YPosition");
lblCoord.Content = DeltaX + ":" + DeltaY;
}
}
private void Label_MouseUp(object sender, MouseButtonEventArgs e)
{
Label l = e.Source as Label;
if (l != null)
{
l.ReleaseMouseCapture();
BasePoint.X += DeltaX;
BasePoint.Y += DeltaY;
DeltaX = 0.0;
DeltaY = 0.0;
moving = false;
lblCoord.Content = BasePoint.X + ":" + BasePoint.Y;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
This is all working as expected until I change XAML, and create two labels during runtime from code behind:
<Canvas>
<ItemsControl Name="btnTableImageList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="Label"
Background="ForestGreen"
Padding="12,7"
Canvas.Left="{Binding XPosition}"
Canvas.Top="{Binding YPosition}"
MouseDown="Label_MouseDown"
MouseUp="Label_MouseUp"
MouseMove="Label_MouseMove"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
Everything else remains the same, except generating those labes from code behind and changed XAML. Now if I hold MouseDown on label and move, nothing happens but MouseDown, and MouseMove are working since I can see test messages in lblCoord.Content.
If you need I can show you label generation code, but it's nothing special, just a class with a for-loop to create certain number of labels and I am calling that on WindowLoaded with btnTableImageList.ItemsSource = tableLbl.CreateTableLabels();.
Anyone have idea why is this happening, or to be more precise, what am I doing wrong?
The standard ItemsPanel of an ItemsControl is a StackPanel, so you will not see any effect when changing Canvas.Left/Top. You can modify the ItemsPanel to provide a Canvas instead.
<Canvas>
<ItemsControl Name="btnTableImageList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="Label"
Background="ForestGreen"
Padding="12,7"
Canvas.Left="{Binding XPosition}"
Canvas.Top="{Binding YPosition}"
MouseDown="Label_MouseDown"
MouseUp="Label_MouseUp"
MouseMove="Label_MouseMove"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
NOTE: there is still something missing; for a complete solution see Setting Canvas properties in an ItemsControl DataTemplate
I think what you're trying to do here is a big step beyond your first piece of code. Because it touches on a number of wpf concepts you probably haven't looked at.
Stackpanel is only part of the thing you need to understand here.
I recommend you download and install snoop.
When you run it you get a weird toolbar with some scope sight things on it.
Drag one over your window and a new window appears.
Hover over where one of your labels is and press shift+ctrl at the same time.
You will see the tree of controls.
Your label will be inside a contentpresenter and it is this which goes directly in the canvas. It is this you need to set canvas.top and canvas.left on.
You will notice when you try and manipulate that control that working in this was is a nuisance.
Why is this so hard? ( you are likely to think ).
That's because you're intended to use binding and templating rather than directly add controls.
Hence, set the datacontext of the window to a viewmodel, bind an observablecollection of viewmodels from there to the itemssource of the itemscontrol. Each of these latter viewmodels would then expose left and top properties which are used to position your piece of ui ( label ).
Here's a working sample illustrates the approach of binding and templating:
https://1drv.ms/u/s!AmPvL3r385QhgooJ94uO6PopIDs4lQ
That's templating into textboxes and stuff rather than doing exactly what you're trying to do.

Convert Code behind MouseButtonEventArgs into MVVM

Im still new to MVVM but im trying to learn it.
I need to resize the Drawn rectangle into my canvas. But i want to do it in MVVM way.
I found this post and its working and it suite my needs but its not MVVM
https://denisvuyka.wordpress.com/2007/10/15/wpf-simple-adorner-usage-with-drag-and-resize-operations/
I am trying to convert this code into MVVM
// Handler for element selection on the canvas providing resizing adorner
void myCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Remove selection on clicking anywhere the window
if (selected)
{
selected = false;
if (selectedElement != null)
{
// Remove the adorner from the selected element
aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
selectedElement = null;
}
}
// If any element except canvas is clicked,
// assign the selected element and add the adorner
if (e.Source != myCanvas)
{
_isDown = true;
_startPoint = e.GetPosition(myCanvas);
selectedElement = e.Source as UIElement;
_originalLeft = Canvas.GetLeft(selectedElement);
_originalTop = Canvas.GetTop(selectedElement);
aLayer = AdornerLayer.GetAdornerLayer(selectedElement);
aLayer.Add(new ResizingAdorner(selectedElement));
selected = true;
e.Handled = true;
}
}
I am using Devexpress MVVM
and this is my xaml code
<Canvas Name="myCanvas" Background="Gray">
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand Command="{Binding OnSelectedCommand , Source={x:Static vm:Vm.instance}}"
EventName="MouseDown" CommandParameter="{Binding ElementName=myCanvas}"
PassEventArgsToCommand="True">
</dxmvvm:EventToCommand>
</dxmvvm:Interaction.Behaviors>
</Canvas >
i am trying to pass the canvas into my view model.
this is the code for my ViewModel
public DelegateCommand<Canvas> OnSelectedCommand { get; private set; }
public Vm()
{
OnSelectedCommand = new DelegateCommand<Canvas>(OnSelectedEvent, true);
}
Canvas c = new Canvas();
private void OnSelectedEvent(object e)
{
if (e == null) return;
c = e as Canvas;
// Remove selection on clicking anywhere the window
if (selected)
{
selected = false;
if (selectedElement != null)
{
// Remove the adorner from the selected element
aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
selectedElement = null;
}
}
// If any element except canvas is clicked,
// assign the selected element and add the adorner
if (e.Source != myCanvas)
{
_isDown = true;
_startPoint = e.GetPosition(myCanvas);
selectedElement = e.Source as UIElement;
_originalLeft = Canvas.GetLeft(selectedElement);
_originalTop = Canvas.GetTop(selectedElement);
aLayer = AdornerLayer.GetAdornerLayer(selectedElement);
aLayer.Add(new ResizingAdorner(selectedElement));
selected = true;
e.Handled = true;
}
}
currently my code doesn't work. In the code behind i have the MouseButtonEventArgs which is needed in the method.
the problem i am facing now is how do it access the MouseButtonEventArgs?
Without it. I can't use the sample code from the blog
You can use Behavior to implement your goal.
Firstly, add the following two assemblies reference to your project:
System.Windows.Interactivity.dll
Microsoft.Expression.Interactions.dll
Add the following xaml namespace:
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Then add behaviors for the Canvas control like this:
<Canvas>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<ei:CallMethodAction MethodName="OnLeftButtonClicked" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
...
</Canvas>
EventTrigger is a trigger behavior that will listen for a specific event raised up. CallMethodAction is an action that will invoke a method which is specified by TargetObject and MethodName properties. Here, the value of TargetObject is {Binding}, it means the target object is your view model, so it will invoke a method with such a name from your view model.
Then, in your view model, add the following method:
public void OnLeftButtonClicked(object s,MouseButtonEventArgs e)
{
}
Notice:
The modifier should be public;
The method name should be identical to the MethodName property of CallMethodAction;

Bind margin inside datatemplate is not updating

Im trying to bind my usercontrols (that have a move on mouse left btn logic inside its code behind) margin inside a datatemplate of an itemscontrol. However when being bound inside the datatemplate it doesnt update itself when a move on the grid behind it (which itself is a usercontrol aswell and has a move on mouse middle button logic inside its code behind).
The code behind of those is: (where in the grid it reacts to middle mouse)
private void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
isDragging = true;
var draggableControl = sender as UserControl;
lasttransform = new Point(0, 0);
clickPosition = e.GetPosition(this.Parent as UIElement);
draggableControl.CaptureMouse();
}
private void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isDragging == true)
{
isDragging = false;
var draggable = sender as UserControl;
draggable.ReleaseMouseCapture();
completetransform = Point.Add(completetransform,new Vector( lasttransform.X, lasttransform.Y));
lasttransform = new Point(0, 0);
}
}
private void Control_MouseMove(object sender, MouseEventArgs e)
{
var draggableControl = sender as UserControl;
if (isDragging && draggableControl != null && draggableControl.GetType() == typeof(BaseNode))
{
Point currentPosition = e.GetPosition(this.Parent as UIElement);
currentPosition = new Point(currentPosition.X, currentPosition.Y);
var transform = draggableControl.RenderTransform as TranslateTransform;
if (transform == null)
{
transform = new TranslateTransform();
draggableControl.RenderTransform = transform;
}
transform.X = completetransform.X + (currentPosition.X - clickPosition.X);
transform.Y = completetransform.Y + (currentPosition.Y - clickPosition.Y);
lasttransform = new Point((currentPosition.X - clickPosition.X), (currentPosition.Y - clickPosition.Y));
}
}
}
When i now want to show my controls in some window like:
<Grid>
<my:EventGrid x:Name="XDisplayedEventGrid" Margin="-20" DataContext="{Binding DisplayedEventGrid}"/>
<Grid Background="Red" Width="100" Height="100" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="{Binding DisplayedEventGrid.ActualTransform, Converter={StaticResource ResourceKey=PointToMarginConverter}}"/>
<my:BaseNode HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Height="100" Margin="{Binding DisplayedEventGrid.ActualTransform, Converter={StaticResource ResourceKey=PointToMarginConverter}}"/>
<ItemsControl ItemsSource="{Binding DisplayedNodes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:BaseNode HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Height="100" Margin="{Binding ElementName=XDisplayedEventGrid, Path=ActualTransform, Converter={StaticResource ResourceKey=PointToMarginConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Where 'my:EventGrid 'is the movable grid, 'my:BaseNode' is a control to test everything and DataTemplate> my:BaseNode .../> /DataTemplate> is the actual displaying control for these usercontrols.
What should happen:
1) When you move the grid behind the visual brush makes it look like you can scroll unlimitedly.
2) The 'Grid Background="Red"' stays at its place on the eventgrid.
3) The Margin is bound to the eventgrid's moved coordinates which is translated to a margin by a converter, so for the user it looks like the controls stay on place on the grid and get moved out of view if the user looks at another portion of the grid.
4) When a node is moved its RenderTransform is set so it can be placed by and bound to a margin while being able to reposition it locally with the RenderTransform.
My Problem now is, that when the test-node has no DataContext bound it has the behavior of the grid and moves accordingly, but if it has one set it acts like the node inside the datatemplate and stays at 0,0 of the window.
The Datacontext of the node is an empty class deriving of a standard implementation of INotifyPropertyChanged.
When not having a datacontext bound inside the datatemplate it still has the same wrong behavior.
Where is my error and
are there any better controls to store externally marged usercontrols?
Thanks
ShinuSha

Move behavior inside ItemsControl

I've got a Collection of items bound to an ItemsControl element.
I need to enable the mouse dragging behavior for each element but I can't seem to make it work.
Adding this code
<i:Interaction.Behaviors>
<el:MouseDragElementBehavior/>
</i:Interaction.Behaviors>
will make it work but only if I put my items outside the ItemsControl.
Here's the code of the ItemsControl component:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<utils:TemplateSelector Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Am I doing something wrong?
EDIT: New code but still not working (with and without the Canvas)
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<utils:TemplateSelector Content="{Binding}">
<i:Interaction.Behaviors>
<el:MouseDragElementBehavior/>
</i:Interaction.Behaviors>
</utils:TemplateSelector>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Seems like that once you start the bounty you suddenly find a solution which you were not able to find in the past months.
I'm answering to add my solution, that currently works but surely can be done better.
What I'm proposing is based on two different sources:
A gesture-driven Windows Phone to-do application by Colin Eberhardt
How to find element in visual tree? wp7 answered by E.Z. Hart
Let's start with the XAML:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="{StaticResource PhoneForegroundBrush}">
<utils:TemplateSelector Content="{Binding}"/>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
DragStarted="GestureListener_OnDragStarted"
DragDelta="GestureListener_OnDragDelta"
DragCompleted="GestureListener_OnDragCompleted"/>
</toolkit:GestureService.GestureListener>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="WidgetsCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
(toolkit comes from Windows Phone Toolkit)
Now let's see the implementation of the three drag handlers.
private int _zindex; //Used to keep the moved element on the top, not working for now
private FrameworkElement _movedObject; //The element that we're moving. Used to avoid moving multiple items if they overlap
private void GestureListener_OnDragStarted(object sender, DragStartedGestureEventArgs e)
{
if (_movedObject != null) return; // We're already moving something!
// Initialize the drag
var fe = sender as FrameworkElement; // The element that we want to move
(fe as Border).BorderThickness = new Thickness(5); // A simple effect to mark the element on the screen
_movedObject = fe; // We set the current object to the one which is moving
Canvas.SetZIndex(fe, _zindex++); // This should take the moved object on the top but it's not working
}
private void GestureListener_OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
var fe = sender as FrameworkElement;
if (!fe.Equals(_movedObject)) return; // We change the object's position only if this is the one who started the event
var offset = DragManager.GetOffset(fe); // We get the current position
var canvas = DragManager.FindChild<Canvas>(Application.Current.RootVisual, "ItemsCanvas"); // We need the container of our object to force it to stay inside the container
//The new position is given by the old one plus the change reported by the event
var horizontalOffset = offset.HorizontalValue + e.HorizontalChange;
var verticalOffset = offset.VerticalValue + e.VerticalChange;
// We need to check if the new position is outside our container's bounds
if (horizontalOffset < 0) horizontalOffset = 0;
else if (horizontalOffset > (canvas.ActualWidth - fe.ActualWidth)) horizontalOffset = canvas.ActualWidth - fe.ActualWidth;
if (verticalOffset < 0) verticalOffset = 0;
else if (verticalOffset > (canvas.ActualHeight - fe.ActualHeight)) verticalOffset = canvas.ActualHeight - fe.ActualHeight;
// Once we've got everything set, we can move our component
DragManager.SetOffset(fe, horizontalOffset, verticalOffset);
}
private void GestureListener_OnDragCompleted(object sender, DragCompletedGestureEventArgs e)
{
var fe = sender as FrameworkElement;
(fe as Border).BorderThickness = new Thickness(0); // We undo our effect
_movedObject = null; // The movement is done so we can reset our current object and wait for a new one to come
}
the handlers work with a class that I called DragManager. Its code it's quite simplet:
public static class DragManager
{
public static void SetOffset(FrameworkElement fe, double horizontalOffset, double verticalOffset)
{
var trans = new TranslateTransform
{
X = horizontalOffset,
Y = verticalOffset
};
// I don't know what may change, in terms of performance, between applying the transform or just changing the margins. I'm using the margins because the transform may be needed for some other purpose
//fe.RenderTransform = trans;
fe.Margin = new Thickness(horizontalOffset, verticalOffset, 0, 0); // We just change our object's margins to reflect its new position
// We store the current position in the objects Tag (maybe there's a better solution but I'm quite new to C#/xaml)
fe.Tag = new Offset
{
VerticalValue = verticalOffset,
HorizontalValue = horizontalOffset,
Transform = trans
};
}
public static Offset GetOffset(FrameworkElement fe)
{
if (fe.Tag == null) fe.Tag = new Offset();
return (Offset)fe.Tag;
}
public struct Offset
{
public double HorizontalValue { get; set; }
public double VerticalValue { get; set; }
public TranslateTransform Transform { get; set; }
}
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null)
{
return null;
}
T foundChild = null;
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
var childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null)
{
break;
}
}
else if (!String.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
// Need this in case the element we want is nested
// in another element of the same type
foundChild = FindChild<T>(child, childName);
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
}
As I already said, this code is working right now.
If anybody has some suggestion to improve it, please write it here so that I can see/test a better way to do this kind of stuff!

Finding Child Objects in ViewBox/Canvas Object

I've implemented a drag and drop that is mostly working in my Silverlight 4 app. Once the users have dropped the shapes onto the canvas, I wanted to use Size & Child Decorators. I've tried to implement the sample code. Code below is problem section of much larger app.
xmal -
<ScrollViewer Grid.RowSpan="1" Grid.Row="2" Grid.ColumnSpan="2" Grid.Column="2" Name="scrollViewer">
<Viewbox Margin="0" MinWidth="400" MinHeight="500"
HorizontalAlignment="Left" VerticalAlignment="Top" Name="ViewBoxTestBuild">
<Canvas x:Name="Camera1Canvas" telerikDragDrop:RadDragAndDropManager.AllowDrop="True"
Width="1200" Height="768" MouseLeftButtonDown="Camera1Canvas_MouseLeftButtonDown">
<Image x:Name="Camera1Image" Source="timemagadj.jpg" Canvas.ZIndex="-1"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<local:Three_Line_Graphic x:Name="threeLineEditTool"
HorizontalAlignment="Left" Canvas.Left="594" Canvas.Top="621" />
<l:Adorner x:Name="adorn" Canvas.ZIndex="100" />
</Canvas>
</Viewbox>
</ScrollViewer>
c# code -
private void Camera1Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sendingObj = sender as Canvas;
if (sendingObj == null) return;
foreach (UserControl l in VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), sendingObj))
{
if (l.Parent == Camera1Canvas )
{
adorn.AdornedElement = l as FrameworkElement;
adorn.adorned_MouseLeftButtonDown(l, e);
break;
}
}
base.OnMouseLeftButtonDown(e);
}
My problem is that when VisualTreeHelper.FindElementsInHostCoordinates is called on left mouse click event, it returns no elements when I click on any object. I sure that it is a coordinate mapping issue, but as this is new ground for me, I'm unsure how to fix it.
Maybe in your case there is no need to use the VisualTreeHelper method.
Since you control the elements inside the canvas and you know their type, you can try something like this:
private void Camera1Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sendingObj = sender as Canvas;
if (sendingObj == null) return;
foreach (UserControl l in sendingObj.Children)
{
var position = e.GetPosition(l);
var lArea = new Rect(0,0,l.ActualWidth,l.ActualHeight);
if (lArea.Contains(position))
{
adorn.AdornedElement = l as FrameworkElement;
adorn.adorned_MouseLeftButtonDown(l, e);
break;
}
}
base.OnMouseLeftButtonDown(e);
}
Which is more efficient than hit-testing. However, this only works if rectangular areas are acceptable.
If you need to detect shapes other than rectangles, you can use the following, as long as you are filling a canvas with instances of UserControl:
private void Camera1Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sendingObj = sender as Canvas;
if (sendingObj == null) return;
var elements = VisualTreeHelper
.FindElementsInHostCoordinates(
e.GetPosition(sendingObj), sendingObj);
foreach (var l in elements)
{
if (l is UserControl)
{
adorn.AdornedElement = l as FrameworkElement;
adorn.adorned_MouseLeftButtonDown(l, e);
break;
}
}
base.OnMouseLeftButtonDown(e);
}
You need to change your VisualTreeHelper line to
GeneralTransform transform = sendingObj.TransformToVisual(Application.Current.RootVisual);
Point pnt = transform.Transform(e.GetPosition(sendingObj));
var elements = VisualTreeHelper.FindElementsInHostCoordinates(pnt,Application.Current.RootVisual);

Categories