How to set position of a User Control [duplicate] - c#

I am new in WPF, I created a new UserControl MyUserControl.
Now I am surprised: the UserContol does not have a location.
How can I read (by code) myUserControl1.Location in the parent container?
I explain:
I have some Dots (UserControls) that the user can drag in a panel. Actually, I am not sure what kind of Panel this will be... Perhaps Grid.
Now, these dots should be linked with a Line.
Actually, I have a Dot.Head and Dot.Queue properties (also Dots). So, when a Head or Queue is added, I need to dinamically create a link (Line) between them [A]-----[B]. This for this Line I search the Start and End points to set.
Control XAML:
<UserControl x:Class="LinePlan.Stop"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="21" d:DesignWidth="80">
<Canvas>
<Path Fill="LightBlue" Width="16" Height="16">
<Path.Data>
<EllipseGeometry x:Name="Dot" Center="8,8"
RadiusX="4" RadiusY="4"/>
</Path.Data>
</Path>
<TextBlock x:Name="StopText" Text="Eiffel Tower" Canvas.Left="16"/>
</Canvas>
</UserControl>
Code:
public partial class Stop : UserControl
{
private Stop head;
private Stop tail;
private LineGeometry headLine;
private LineGeometry queueLine;
public Stop()
{
InitializeComponent();
}
public Stop Head
{
get { return head; }
set
{
if (head != value)
{
head = value;
if (head == null)
{
if (headLine != null)
headLine = null;
}
else
{
headLine = new LineGeometry();
headLine.StartPoint = head.DotPosition;
headLine.EndPoint = this.DotPosition;
// ?? Add this line to the parent
}
}
}
}
public Stop Tail
{
get { return tail; }
set { tail = value; }
}
public Point DotPosition
{
get
{
double x = Canvas.GetLeft(this) + this.Dot.Center.X;
double y = Canvas.GetTop(this) + this.Dot.Center.Y;
return new Point(x, y);
}
set
{
Canvas.SetLeft(this, value.X - this.Dot.Center.X);
Canvas.SetTop(this, value.Y - this.Dot.Center.Y);
}
}
}

The WPF layout system doesn't use absolute positioning, unless you're placing your controls on a container that supports absolute positioning (typically a Canvas). If you're using a Canvas, you can get or set the position of the control using the Canvas.Left, Canvas.Right, Canvas.Top and Canvas.Bottom attached properties:
double x = Canvas.GetLeft(myControl);
double y = Canvas.GetTop(myControl);
Now, if you want the actual location of the control (relative to its parent), you can use the VisualTreeHelper.GetOffset method:
Vector offset = VisualTreeHelper.GetOffset(myControl);
double x = offset.X;
double y = offset.Y;

Elements (like user controls) are normally placed in panels in WPF. Depending on which panel you are using the panel may add some attached properties to the user control. If the user control is placed in a Canvas it will get the attached properties Left, Top, Right and Bottom. However, if the user control is placed in a Grid it will get the attached properties Row and Column (and some more). Other panels like StackPanel will not attach any properties. There is no such thing as a universal user control location.
Panels Overview
Attached Properties Overview
Assuming that you are using a Canvas as your panel you can access the attached Left and Top properties like this:
Double x = Canvas.GetLeft(myUserControl1);
Double y = Canvas.GetTop(myUserControl1);

Related

How to access my total x translation within a pan gesture recognizer and access it from XAML

I'm setting up a pan gesture recognizer that, when panning with two fingers, moves the view contained in it. I want to provide event triggers when the view has moved a specified amount in the X direction, which requires the total displacement to be sent to my XAML page every time it is changed in the gesture recognizer class. I am having trouble accessing the total x displacement outside of the gesture recognizer class.
I have tried using a delegate and event handler to get the .TranslationX or TotalX values from within the class, but cannot seem to figure out how to do so. Every approach I try does not update the value as I pan the view. Below is my Gesture Container class. I just need to access the total x displacement every time it changes outside this class.
public class GestureContainer : ContentView
{
public GestureContainer()
{
var panGesture = new PanGestureRecognizer();
panGesture.TouchPoints = 2;
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);
}
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Running:
Content.TranslationX =
e.TotalX;
break;
case GestureStatus.Completed:
ViewExtensions.TranslateTo(Content, 0, 0, 250, null);
break;
}
}
}
I am very new to C# and Xamarin, so I apologize if the solution is an obvious one. I'm just not sure how to approach this one.
I want to provide event triggers when the view has moved a specified amount in the X direction, which requires the total displacement to be sent to my XAML page every time it is changed in the gesture recognizer class. I am having trouble accessing the total x displacement outside of the gesture recognizer class.
If you want to get view TranslationX and TranslationY after you move view, I do one sample you can take a look:
Firstly, you can create a pan container,contains a generalized helper class that performs freeform panning.
public class PanContainer : ContentView
{
public double x { get; set; }
public double y { get; set; }
public PanContainer ()
{
// Set PanGestureRecognizer.TouchPoints to control the
// number of touch points needed to pan
var panGesture = new PanGestureRecognizer ();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add (panGesture);
}
void OnPanUpdated (object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType) {
case GestureStatus.Running:
// Translate and ensure we don't pan beyond the wrapped user interface element bounds.
Content.TranslationX = Math.Max (Math.Min (0, x + e.TotalX), -Math.Abs (Content.Width - App.ScreenWidth));
Content.TranslationY = Math.Max (Math.Min (0, y + e.TotalY), -Math.Abs (Content.Height - App.ScreenHeight));
break;
case GestureStatus.Completed:
// Store the translation applied during the pan
x = Content.TranslationX;
y = Content.TranslationY;
Console.WriteLine("TranslationX is {0}",x);
Console.WriteLine("Translationy is {0}", y);
break;
}
}
}
Then shows the PanContainer wrapping an one element, here I use Image element.
<ContentPage.Content>
<AbsoluteLayout>
<local:PanContainer x:Name="pan1">
<Image
x:Name="image1"
HeightRequest="1024"
Source="MonoMonkey.jpg"
WidthRequest="768" />
</local:PanContainer>
<Label x:Name="labelx" Text="{Binding Source={x:Reference image1}, Path=TranslationX}" />
<Label
x:Name="labely"
Margin="30"
Text="{Binding Source={x:Reference image1}, Path=TranslationY}" />
</AbsoluteLayout>
</ContentPage.Content>
You can use bind to get image TranslationX and TranslationY.

How to set boundary of a TextBlock in a grid?

I have few textblocks in a grid which can be dragged. I want to restrict the user so that user can't drag a textblock outside the grid.
I've tried a few ways like getting the position of the grid so that somehow i can control but it didn't work as expected.
Thanks in advance.
This can be done pretty easily using a Canvas inside the Grid, calculating the coordinates of the TextBlock inside of the Canvas and then continuously checking whether the TextBlock is still within its bounds. When the TextBlock leaves its bounds, the Transform then reverts back to it's last known 'good' coordinates.
XAML
<Grid>
<Grid Name="GridBounds" Width="600" Height="600" Background="Aqua">
<Canvas>
<TextBlock Name="TextBlock1" Text="Drag Me" FontSize="40" ManipulationDelta="TextBlock1_ManipulationDelta" ManipulationMode="TranslateX, TranslateY" HorizontalAlignment="Stretch" Canvas.Left="216" Canvas.Top="234" VerticalAlignment="Stretch"/>
</Canvas>
</Grid>
</Grid>
Code Behind
CompositeTransform TextDrag = new CompositeTransform();
CompositeTransform savedTransform = new CompositeTransform();
public MainPage()
{
this.InitializeComponent();
TextBlock1.RenderTransform = TextDrag;
}
// Copy Transform X and Y if TextBlock is within bounds
private void CopyTransform(CompositeTransform orig, CompositeTransform copy)
{
copy.TranslateX = orig.TranslateX;
copy.TranslateY = orig.TranslateY;
}
private void TextBlock1_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
TextDrag.TranslateX += e.Delta.Translation.X;
TextDrag.TranslateY += e.Delta.Translation.Y;
CompositeTransform transform = TextBlock1.RenderTransform as CompositeTransform;
// Get current Top-Left coordinates of TextBlock
var TextTopLeft = TextBlock1.TransformToVisual(GridBounds).TransformPoint(new Point(0, 0));
// Get current Bottom-Right coordinates of TextBlock
var TextBottomRight = TextBlock1.TransformToVisual(GridBounds).TransformPoint(new Point(TextBlock1.ActualWidth, TextBlock1.ActualHeight));
// Get Top-Left grid coordinates
var GridTopLeft = (new Point(0, 0));
// Get Bottom-Right grid coordinates
var GridBottomRight = (new Point(GridBounds.ActualWidth, GridBounds.ActualHeight));
if (TextTopLeft.X < GridTopLeft.X || TextBottomRight.X > GridBottomRight.X)
{
// Out of bounds on X axis - Revert to copied X transform
TextDrag.TranslateX = savedTransform.TranslateX; ;
}
else if (TextTopLeft.Y < GridTopLeft.Y || TextBottomRight.Y > GridBottomRight.Y)
{
// Out of bounds on Y axis - Revert to copied Y transform
TextDrag.TranslateY = savedTransform.TranslateY;
}
else
{
// TextBlock is within bounds - Copy X and Y Transform
CopyTransform(transform, savedTransform);
}
}
Here it is in action.

Relocate children in grid

I'm trying to realize a chess game in C# with WPF. By now I visualized the chess grid and the figures in it. My .xaml file contains just a grid (named "playground") which is 8x8. The initializing in the .xaml.cs file looks like the following:
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
Border border = new Border();
if (y % 2 == (x % 2 == 0 ? 0 : 1))
{ // Chess like look
border.Background = black; //black is a static SolidColorBrush
}
// a ChessTile is an Image which can be a Figure or an EmptyTile
// omitted code... evaluate whether what figure the tile is or empty
ChessTile tile;
Grid.SetColumn(border, x);
Grid.SetRow(border, y);
border.HorizontalAlignment = HorizontalAlignment.Stretch;
border.VerticalAlignment = VerticalAlignment.Stretch;
if (tile is Figure)
{
// Set Event to border so the user is able to click outside the image
border.MouseDown += ClickFigure;
}
border.Child = tile; // Set tile as Child
playground.Children.Add(border); // Add border to Child
}
}
After initializing I want to move a chess figure to another Column and or Row in the grid. Currently I am evaluating in ClickFigure on which empty tile the figure is able to move to, then adding a new Handler to these empty tiles and moving my Figures there. I'm calling tile.Move(otherTile) in the Handler:
(from ChessTile.cs)
public Vector Coordinates
{
get
{
return position; //private Vector
}
private set
{
position = value;
Grid.SetRow(this, (int)position.X); //relocate this (ChessTile)
Grid.SetColumn(this, (int)position.Y);
}
}
public void Move(ChessTile other)
{
Vector temp = position;
Coordinates = other.position; //relocating both tiles through properties
other.Coordinates = temp;
}
The problem now is that the grid does not do anything at all. I searched for some method to update/repaint the grid manually but I didn't find anything. I also tried to remove the child first and then to add it again but that gave me an error saying that the child was already bound to an UIElement and that I would have to seperate it first.
Do you have any idea what I am doing wrong?
You need to change the row and column on the original Border placed inside the Grid.
In your setup code, you set column and row on a border object:
Grid.SetColumn(border, x);
Grid.SetRow(border, y);
But then in your Coordinates property, you're setting the column and row on this.
Grid.SetRow(this, (int)position.X); //relocate this (ChessTile)
Grid.SetColumn(this, (int)position.Y);
Assuming this is a custom class you've written, then you need to make sure it has a reference to the original Border and change your Coordinate setter to:
Grid.SetRow(border, (int)position.X); //relocate the border!
Grid.SetColumn(border, (int)position.Y);
Notice the change from this => border.

WPF moving border around on screen

I'm making a game inventory system where I have a grid and borders that take up a grid space have a background image of the icon. For example in grid position 0,0 I have a border with a background of a health potion. I want to be able to drag this border to any other grid location. Right now I'm working on just clicking and dragging the border to follow the mouse.
What I have so far sort of works but not really. If sort of freaks out when I left click on the icon and move slowly. However I can move out of the icon fast and it doesn't keep up so it then stops moving until I'm back over it. Any ideas on how to get this kind of functionality?
public partial class MainWindow : Window
{
TranslateTransform trans = new TranslateTransform();
Border border;
public MainWindow()
{
InitializeComponent();
// create a broder and set it's background image
border = new Border();
border.Visibility = System.Windows.Visibility.Visible;
var img = (Image)MasterGrid.FindResource("notepad");
var imgBrush = new ImageBrush(img.Source);
border.Background = imgBrush;
border.Margin = new Thickness(2.0);
// add the border to the grid
Grid.SetRow(border, 0);
Grid.SetColumn(border, 1);
Grid.SetRowSpan(border, 2);
Grid.SetColumnSpan(border, 1);
InvGrid.Children.Add(border);
// hook up events
_00.MouseUp += new System.Windows.Input.MouseButtonEventHandler(_00_MouseUp);
border.MouseDown += border_MouseDown;
border.MouseMove += border_MouseMove;
}
void border_MouseMove(object sender, MouseEventArgs e)
{
// make the border follow the mouse position
trans.X = e.GetPosition(border).X;
trans.Y = e.GetPosition(border).Y;
}
void border_MouseDown(object sender, MouseButtonEventArgs e)
{
border.RenderTransform = trans;
}
private void _00_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
MessageBox.Show("test");
}
}
It's not that easy, so I will give you some steps:
You should know sizes of cells, then you are able to calculate position of each one.
Prepare a container (a Grid for example), which will be only for moving purpose.
When you start dragging put the item in the container (2.), set its position under your cursor and hide the cell's content.
When you drop the item, calculate over which cell you are (maybe there is even a function to get a control under the cursor) and do the rest of logic.
I'm not sure if dragging is possible in case of regular controls, maybe you will have to replace your Grid with Canvas.
Try something like this:
<Grid>
<Border x:Name="DragContainer" Width="40" Height="40" />
<!-- OTHER STUFF - YOUR GRID WITH ITEMS -->
</Grid>
and modify only Border's Margin.
Another way to do it is to do dragging inside Canvas, then you will be able to set Canvas.Left, Canvas.Top - it should work properly.
<Canvas>
<Border x:Name="DragContainer" Width="40" Height="40" />
<!-- OTHER STUFF - YOUR GRID WITH ITEMS -->
</Canvas>

Check if usercontrol is within Window Bounds

I have a project that requires the ability to drag a user control (hosted within a grid) around the screen. This works just fine with the below code:
void MyUserControl_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
var ct = (CompositeTransform) RenderTransform ?? new CompositeTransform {CenterX = 0.5, CenterY = 0.5};
ct.TranslateX += e.Delta.Translation.X;
ct.TranslateY += e.Delta.Translation.Y;
}
The issue is that the user control can be dragged all the way out of the screen area. To prevent this, I tried the example shown here: http://msdn.microsoft.com/en-us/library/system.windows.uielement.manipulationdelta.aspx
Unfortunately, it uses containingRect.Contains(shapeBounds) whereas in windows 8, we are expected to replace shapeBounds(this is a rect) with a Point. I am not sure on how to work with this.
So the question is how can we ensure that a user control or any UIElement cannot be dragged out of the Window.Current.Bounds area in windows 8 store app?
Thanks.
EDIT: More details on the xaml structure:
The mainpage contains a Grid with horizontal and vertical alignment set to stretch. Usercontrols are added to this grid as required. Each usercontrol has a parent grid that contains 3 different views (fullscreen, window and small). The views are shown based on the user's choice. The drag behavior must be applied only when the window grid is shown. So we have this
<Grid> <!-- this is the parent grid on mainpage with horizontal and vertical alignment to stretch-->
<Grid> <!-- this is the usercontrol's main grid (added to above grid via code). This grid must be draggable if the below grid is window -->
<Grid /> <!-- this is one of the child grids that is shown based on user's choice (fullscreen, window or small view).-->
</Grid>
</Grid>
I dont have much choice in changing the layout. Using the above ManipulationDelta event in the usercontrol (which is enabled/disabled based on the child grid shown), I am able to get the drag behavior, but the control can go out of window bounds. So is there any way we can add the below FlickBehavior in WinRTXamlToolkit through code instead of xaml OR enable/disable the behavior based on some condition?
<i:Interaction.Behaviors>
<views:HeavyFlickBehavior />
</i:Interaction.Behaviors>
You can check the FlickBehavior in WinRT XAML Toolkit for an example of how to achieve that with a parent Canvas and Canvas.Top/Left properties instead of RenderTransform, assuming the Canvas defines your bounds.
Here's an abstract:
private void OnAssociatedObjectManipulationStarting(object sender, ManipulationStartingRoutedEventArgs e)
{
_startPosition = new Point(
Canvas.GetLeft(this.AssociatedObject),
Canvas.GetTop(this.AssociatedObject));
}
private void OnAssociatedObjectManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs manipulationDeltaRoutedEventArgs)
{
var dx = manipulationDeltaRoutedEventArgs.Cumulative.Translation.X;
var dy = manipulationDeltaRoutedEventArgs.Cumulative.Translation.Y;
var x = _startPosition.X + dx;
var y = _startPosition.Y + dy;
if (manipulationDeltaRoutedEventArgs.IsInertial)
{
while (x < 0 ||
x > _canvas.ActualWidth - this.AssociatedObject.ActualWidth)
{
if (x < 0)
x = -x;
if (x > _canvas.ActualWidth - this.AssociatedObject.ActualWidth)
x = 2 *
(_canvas.ActualWidth - this.AssociatedObject.ActualWidth) -
x;
}
while (y < 0 ||
y > _canvas.ActualHeight - this.AssociatedObject.ActualHeight)
{
if (y < 0)
y = -y;
if (y > _canvas.ActualHeight - this.AssociatedObject.ActualHeight)
y = 2 * (_canvas.ActualHeight - this.AssociatedObject.ActualHeight) -
y;
}
}
else
{
if (x < 0)
x = 0;
if (x > _canvas.ActualWidth - this.AssociatedObject.ActualWidth)
x = _canvas.ActualWidth - this.AssociatedObject.ActualWidth;
if (y < 0)
y = 0;
if (y > _canvas.ActualHeight - this.AssociatedObject.ActualHeight)
y = _canvas.ActualHeight - this.AssociatedObject.ActualHeight;
}
Canvas.SetLeft(this.AssociatedObject, x);
Canvas.SetTop(this.AssociatedObject, y);
}

Categories