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
Related
I am able to dynamically add images into the WPF Canvas control as child elements but failing to drag & drop those images inside the canvas. Please help me as how could i move or drag/drop images inside the canvas window.
Thanks in advance.
Below is what I have done so far:
<Canvas x:Name="canvasImages" Height="325" Margin="0,0,0,0" Width="430"
HorizontalAlignment="Left" VerticalAlignment="Top" AllowDrop="True"
PreviewMouseLeftButtonDown="MouseLeftButtonDown"
PreviewMouseLeftButtonUp="MouseLeftButtonUp"
PreviewMouseMove="MouseMove"
MaxWidth="430" MaxHeight="325"
ScrollViewer.HorizontalScrollBarVisibility="Visible"/>
</Grid>
**Code**
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select Multiple Pictures";
op.Multiselect = true;
op.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.jfif, *.png) |
*.jpg; *.jpeg; *.jpe; *.jfif; *.png";
foreach (string imageFile in op.FileNames)
{
Image img = new Image();
img.Source = new BitmapImage(new Uri(imageFile));
img.Height = 100;
img.Width = 100;
img.AllowDrop = true;
Canvas.SetTop(img, y); //Setting up images to the Top position
Canvas.SetLeft(img, x); //Setting up images to the left position
canvasImages.Children.Add(img);
}
private new void MouseLeftButtonDown(object sender,
MouseButtonEventArgs
e)
{
IsDragging = true;
draggedItem = (UIElement)sender;
itemRelativePosition = e.GetPosition(draggedItem);
}
private new void MouseMove(object sender,
System.Windows.Input.MouseEventArgs e)
{
if (!IsDragging)
return;
Point canvasRelativePosition = e.GetPosition(canvasImages);
Canvas.SetTop(draggedItem, canvasRelativePosition.Y -
itemRelativePosition.Y);
Canvas.SetLeft(draggedItem, canvasRelativePosition.X -
itemRelativePosition.X);
}
private new void MouseLeftButtonUp(object sender, MouseButtonEventArgs
e)
{
if (!IsDragging)
return;
IsDragging = false;
}
Ok, a few things wrong here...
1) Your mouse down handler has to be on the image, not the canvas, otherwise your code has no way of knowing which item is being dragged.
2) Once you've clicked on an image the handler should capture the mouse for the canvas so that you get all mouse move messages.
3) The Canvas MouseMove and MouseUp handlers then need to be handled accordingly.
4) The Canvas needs to have a background. If you don't give it a background then it's effectively transparent to the hit-testing and you won't get mouse messages for it. If you don't want it to have a visible background then set it to Transparent.
So your Canvas tag needs to look like this:
<Canvas x:Name="canvasImages" Height="325" Margin="0,0,0,0" Width="430"
HorizontalAlignment="Left" VerticalAlignment="Top" AllowDrop="True"
PreviewMouseLeftButtonUp="CanvasImages_PreviewMouseLeftButtonUp"
PreviewMouseMove="CanvasImages_PreviewMouseMove"
MaxWidth="430" MaxHeight="325"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
Background="Transparent" />
And every image you create needs to have a handler set for it's MouseDown event:
img.MouseLeftButtonDown += Img_MouseLeftButtonDown;
Then it's just a matter is implementing your handlers like this:
private void Img_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.draggedItem = (UIElement)sender;
itemRelativePosition = e.GetPosition(this.draggedItem);
e.Handled = true;
}
private void CanvasImages_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (this.draggedItem == null)
return;
var newPos = e.GetPosition(canvasImages) - itemRelativePosition;
Canvas.SetTop(this.draggedItem, newPos.Y);
Canvas.SetLeft(this.draggedItem, newPos.X);
canvasImages.CaptureMouse();
e.Handled = true;
}
private void CanvasImages_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (this.draggedItem != null)
{
this.draggedItem = null;
canvasImages.ReleaseMouseCapture();
e.Handled = true;
}
}
#Mark, Here are the XAML code and the respective Class for your perusal. Basically I am allowing multiple selection of images by "OpenFileDialog()" and adding those images dynamically to Canvas control as mentioned in my previous code, which then i am failing to drag around those images internally within the Canvas control.
Below is the XAML Code
<Window x:Class="PicturesMovement.CanvasControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Connectlite Clients"
Height="394" Width="445"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
<Grid Margin="0,0,2,0" Background="{DynamicResource {x:Static
SystemColors.MenuBarBrushKey}}">
<Button x:Name="Select" Content="Select" HorizontalAlignment="Left"
Height="22" Margin="329,328,0,0" VerticalAlignment="Top" Width="42"
Click="SelectImages"/>
<Button x:Name="Cancel" Content="Cancel" HorizontalAlignment="Left"
Margin="374,328,0,0" VerticalAlignment="Top" Width="49"
Click="closeBox"/>
<Canvas x:Name="canvasImages" Height="325" Margin="0,0,0,0"
Width="430" HorizontalAlignment="Left" VerticalAlignment="Top"
AllowDrop="True" PreviewMouseDown="PreviewMouseDown"
PreviewMouseUp="PreviewMouseUp"
PreviewMouseMove="PreviewMouseMove" MaxWidth="430"
MaxHeight="325"
ScrollViewer.HorizontalScrollBarVisibility="Visible"/>
</Grid>
</Window>
Below are the respective Class that triggers those Mouse Events
public partial class CanvasControl : System.Windows.Window,
System.Windows.Markup.IComponentConnector {
this.canvasImages.PreviewMouseDown += new
System.Windows.Input.MouseButtonEventHandler
(this.PreviewMouseDown);
this.canvasImages.PreviewMouseUp += new
System.Windows.Input.MouseButtonEventHandler
(this.PreviewMouseUp);
this.canvasImages.PreviewMouseMove += new
System.Windows.Input.MouseEventHandler
(this.PreviewMouseMove);
}
Any suggestions will be highly appreciated...Thanks
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.
I want a similar behavior like what happens when we tap on a TextBox. The keyboard pops up pushing the TextBox upwards. When I tap a button, the hidden Grid (PushContainer) should pop up pushing the content up. I've tried the following code with no luck. Currently, list view goes all the way down to the bottom underneath the Grid. It just pops up on top of the list view.
<Grid x:Name="MainGrid" Background="#FF97C5C5">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView x:Name="lstView" ItemTemplate="{StaticResource StatusTemplate}" Background="#FFC91010">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Grid x:Name="PushContainer" Visibility="Collapsed" Grid.RowSpan="1" Grid.Row="1" Height="300"/>
</Grid>
Any help would be appreciated.
To slide a control up like the soft keyboard does animate a RenderTransform to shift it to the top. Just changing the Visibility on an object and forcing a new layout will pop rather than slide.
Here's a quick sample. You could also define the storyboard, etc. in Xaml. For demonstration purposes it's controlled by an AppBarToggleButton and will create a CompositeTransform on an element which doesn't have one.
For more information see Storyboarded animations on MSDN.
private void AppBarToggleButton_Checked(object sender, RoutedEventArgs e)
{
// Almost (but not quite) to the top of the page
SlideVertical(MainGrid,-1 * ActualHeight + 100);
}
private void AppBarToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
SlideVertical(MainGrid,0);
}
private void SlideVertical(UIElement target, double endPos)
{
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
// Over how long to perform the animation.
Duration dur = TimeSpan.FromMilliseconds(100);
da.Duration = dur;
sb.Duration = dur;
// From current location to endPos
var ct = target.RenderTransform as CompositeTransform;
if (ct == null)
{
// Give up if we had some other type of transform
if (target.RenderTransform != null)
return;
// That way we don't step on a non-CompositeTransform
// RenderTransform if one already exists.
// That would be bad.
ct = new CompositeTransform();
target.RenderTransform = ct;
}
double startPos = ct.TranslateY;
da.From = startPos;
da.To = endPos;
sb.Children.Add(da);
// Choose the element to slide
Storyboard.SetTarget(da, target);
// Animate the target's CompositeTransform.TranslateY
Storyboard.SetTargetProperty(da, "(UIElement.RenderTransform).(CompositeTransform.TranslateY)");
sb.Begin();
}
I got a working code for scaling frameworkelements in WPF via the ManipulationDelta method. It works with all kind of elements, as buttons, shapes or textblock, but not with textboxes.
Does anybody know how I can fix it?
Edit: Here´s a simpified Version of my Code:
private void canvas_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
var element = e.Source as FrameworkElement;
var transformation = element.RenderTransform as MatrixTransform;
var matrix = transformation == null ? Matrix.Identity : transformation.Matrix;
matrix.ScaleAt(e.DeltaManipulation.Scale.X,
e.DeltaManipulation.Scale.Y,
1,
1);
}
matrix.RotateAt(e.DeltaManipulation.Rotation,
e.ManipulationOrigin.X,
e.ManipulationOrigin.Y);
matrix.Translate(e.DeltaManipulation.Translation.X,
e.DeltaManipulation.Translation.Y);
element.RenderTransform = new MatrixTransform(matrix);
e.Handled = true;
}
The elements are created genericly, but it´s the same as this xaml:
<Canvas Name="SomeCanvas" ManipulationDelta="canvas_ManipulationDelta">
<TextBox Canvas.Left="400" Canvas.Top="200" Height="50" Name="s3" IsManipulationEnabled = "true" Background="#57FF3ACB" />
</Canvas>
This is because TextBox handles MouseDown events internally. They never get called, so the manipulation can't work. Here is the explanation:
http://msdn.microsoft.com/es-es/library/ms750580(v=vs.85).aspx
Alternatively, you could wrap the TextBox in a Border:
<Canvas Name="SomeCanvas" ManipulationDelta="canvas_ManipulationDelta">
<Border Canvas.Left="400" Canvas.Top="200" Background="Transparent" IsManipulationEnabled="True">
<TextBox Height="50" Name="s3" Background="#57FF3ACB" IsHitTestVisible="False" />
</Border>
</Canvas>
Put the IsHitTestVisible property to False in order to let the Mouse events pass from the TextBox to the Border.
Furthermore, you need to set the Border's Background to make it hit test visible.
I'm trying to create a tree header within a TreeView, but nothing will display. In my program, I'm creating a series of ellipse objects on a canvas that have names. What I'm trying to do is this:
When the ellipse object is created, create a header within the TreeView using the ellipse' names.
Since the first ellipse has a name of "Circle01," this is what I'm trying to display in the TreeView when the Left Mouse button is released. But when I do so, nothing happens.
// Ellipse object is created when Left Mouse button is release.
private void _canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (mouseLBDown)
{
mouseLBDown = false;
if (isCreatingEllipse)
{
_debugLabel.Content = "Done.";
isCreatingEllipse = false;
loadToTree = true;
}
}
}
// Suppose to load name of Ellipse as a header, but doesn't.
private void _circleTree_Loaded(object sender, RoutedEventArgs e)
{
if (loadToTree)
{
TreeViewItem item = new TreeViewItem();
item.Header = circle[0].circleName;
_circleTree.Items.Add(item);
loadToTree = false;
}
}
XAML:
<TreeView Name="_boneTree" Background="#404040" Foreground="#E0E0E0"
HorizontalAlignment="Left" Height="422" Margin="970,28,0,0"
VerticalAlignment="Top" Width="212" Loaded="_circleTree_Loaded"/>
You should not create your own TreeViewItems, rather set the TreeView's ItemsSource and let WPF build the TreeViewItems for you. Also it is easier to do this in XAML:
<TreeView Name="_boneTree" Background="#404040" Foreground="#E0E0E0"
HorizontalAlignment="Left" Height="422" Margin="970,28,0,0"
VerticalAlignment="Top" Width="212"
ItemsSource="{Binding Circles}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="Header" Value="{Binding circleName}"/>
</Style>
</TreeView.Resources>
</TreeView>
Where Circles is a property on your DataContext which by default will be your codebehind:
public ObservableCollection<Circle> Circles { get; set; }
Then whenever a Circle is added or removed it will automatically show up on the UI!