I move and scale a rectangle within a canvas element with touch gestures. The code is based on this reference: http://msdn.microsoft.com/en-us/magazine/gg650664.aspx
The XAML code is as follows:
<Canvas Name="canvas" >
<Rectangle x:Name="rectangle" Fill="Green" Height="300" Canvas.Left="0" Stroke="Red" Canvas.Top="0" Width="100" StrokeThickness="3" >
<Rectangle.RenderTransform>
<TransformGroup>
<MatrixTransform x:Name="previousTransform" />
<TransformGroup x:Name="currentTransform">
<ScaleTransform x:Name="scaleTransform" />
<TranslateTransform x:Name="translateTransform" />
</TransformGroup>
</TransformGroup>
</Rectangle.RenderTransform>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener DragStarted="OnGestureListenerDragStarted"
DragDelta="OnGestureListenerDragDelta"
DragCompleted="OnGestureListenerDragCompleted"
PinchStarted="OnGestureListenerPinchStarted"
PinchDelta="OnGestureListenerPinchDelta"
PinchCompleted="OnGestureListenerPinchCompleted" />
</toolkit:GestureService.GestureListener>
</Rectangle>
</Canvas>
Dragging:
void OnGestureListenerDragDelta(object sender, DragDeltaGestureEventArgs args)
{
translateTransform.X += args.HorizontalChange;
translateTransform.Y += args.VerticalChange;
}
During pinching the scale transform is updated:
void OnGestureListenerPinchDelta(object sender, PinchGestureEventArgs args)
{
scaleTransform.ScaleX = args.DistanceRatio;
scaleTransform.ScaleY = args.DistanceRatio;
}
When pinching completes I want to get the "real" size and position of the rectangle within the parent canvas. Therefore I tried to apply the transform to the rectangle boundaries:
void OnGestureListenerPinchCompleted(object sender, PinchGestureEventArgs args)
{
Rect r = currentTransform.TransformBounds(new Rect(Canvas.GetLeft(rectangle), Canvas.GetTop(rectangle), rectangle.Width, rectangle.Height));
rectangle.Width = r.Width;
rectangle.Height = r.Height;
Canvas.SetLeft(rectangle, r.X);
Canvas.SetTop(rectangle, r.Y);
// Reset transforms
previousTransform.Matrix = new Matrix();
rectangle.RenderTransformOrigin = new Point(0, 0);
scaleTransform.ScaleX = scaleTransform.ScaleY = 1;
scaleTransform.CenterX = scaleTransform.CenterY = 0;
translateTransform.X = translateTransform.Y = 0;
}
This works fine for translation, but scaling is wrong (the rectangle is scaled but the factor is too big when increasing the scale, and too little when decreasing the scale). How can I get the correct final size of the scaled rectangle?
Regards
I fixed the problem by ommitting the transformation matrix classes and using the scaling factor and translation values manually.
Nevertheless I would be interested in a solution for using the XAML code above.
Related
I have a Popup which will fill the whole page when opened.
<Grid x:Name="gridRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="Open" HorizontalAlignment="Center" Click="{x:Bind viewModel.OpenPopup}" />
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsOpen}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:MyPopup Width="{Binding ElementName=gridRoot, Path=ActualWidth}" Height="{Binding ElementName=gridRoot, Path=ActualHeight}"/>
</Popup>
</Grid>
The Popup is a UserControl
<Grid Background="Red">
<Button Content="Close" HorizontalAlignment="Center" Click="{x:Bind viewModel.ClosePopup}" />
</Grid>
The page
When popup is shown
Close the popup, resize the page, then reopen the popup. Notice that it does not match the new size of container page even though its Width and Height is bound to gridRoot . Do I have to manually set a new Width and Height for the popup? Why can't I achieve this with binding? This issue also appears on mobile during 'OrientationChanged'
Based on Decade Moon comment, this is how to resize the popup to match the parent container as its size changed.
Create a dependency property in the code behind
public double PageWidth
{
get { return (double)GetValue(PageWidthProperty); }
set { SetValue(PageWidthProperty, value); }
}
public static readonly DependencyProperty PageWidthProperty =
DependencyProperty.Register("PageWidth", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
public double PageHeight
{
get { return (double)GetValue(PageHeightProperty); }
set { SetValue(PageHeightProperty, value); }
}
public static readonly DependencyProperty PageHeightProperty =
DependencyProperty.Register("PageHeight", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
Update the value on SizeChanged event
private void GamePage_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width > 0d && e.NewSize.Height > 0d)
{
PageWidth = e.NewSize.Width;
PageHeight = e.NewSize.Height;
}
}
Then in XAML, just use x:Bind to bind the popup width and height
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsPopupCorrectOpen, Mode=TwoWay}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:PopupCorrect Width="{x:Bind PageWidth, Mode=TwoWay}" Height="{x:Bind PageHeight, Mode=TwoWay}"/>
</Popup>
Pretty straight forward. Just remember not to use the ActualWidth or ActualHeight properties for binding source as they do not raise the PropertyChanged event.
Although it has an ActualWidthProperty backing field, ActualWidth does not raise property change notifications and it should be thought of as a regular CLR property and not a dependency property.
For purposes of ElementName binding, ActualWidth does not post updates when it changes (due to its asynchronous and run-time calculated nature). Do not attempt to use ActualWidth as a binding source for an ElementName binding. If you have a scenario that requires updates based on ActualWidth, use a SizeChanged handler.
#PutraKg have a great way.
But I have two way to solve it.
The first is set the VerticalAlignment="Center" HorizontalAlignment="Center" that can make the popup in the center.
But I think youare not content to put it in the center.
The great way is use the screen position.
You can get the Grid's screen postion and make it to popup.
In open button
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var grid = (UIElement)popupCorrect.Parent; //get grid
var p = grid.TransformToVisual (Window.Current.Content).TransformPoint(new Point(0, 0)); //get point
popupCorrect.HorizontalOffset = p.X;
popupCorrect.VerticalOffset = p.Y;
popupCorrect.IsOpen = !popupCorrect.IsOpen;
}
My idea is to create custom Tile control which is used as Live Tile (rendering it to a bitmap image and use as tile background) and within my app in a LongListSelector. By using the same control for both scenarios it ensures that it will look identical.
Here a short sample of my custom Tile control:
public class Tile : ContentControl
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Content = GetTileLayout();
}
private Grid GetTileLayout()
{
double width = 336;
double height = 336;
StackPanel panel = new StackPanel();
TextBlock contentTextBlock = new TextBlock();
// ...
Grid layoutRoot = new Grid();
layoutRoot.Background = new SolidColorBrush(Colors.Red);
layoutRoot.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
layoutRoot.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
layoutRoot.Width = width;
layoutRoot.Height = height;
layoutRoot.Children.Add(panel);
layoutRoot.Measure(new Size(width, height));
layoutRoot.Arrange(new Rect(0, 0, width, height));
layoutRoot.UpdateLayout();
return layoutRoot;
}
}
If I want to use it in the LongListSelector then it needs to be scaled down from 336x336 to 200x200 pixels. I tried it with a simple ScaleTransform but the result is not the same as I expected.
<phone:LongListSelector x:Name="ItemList" ItemsSource="{Binding Items}" LayoutMode="Grid" GridCellSize="222,222">
<phone:LongListSelector.ItemTemplate>
<StackPanel Margin="12,12,0,0" Background="Blue">
<DataTemplate>
<common:Tile VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<common:Tile.RenderTransform>
<ScaleTransform ScaleX="0.5" ScaleY="0.5" />
</common:Tile.RenderTransform>
</common:Tile>
</DataTemplate>
</StackPanel>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
This is the result:
The red rectangle is my custom Tile control with the ScaleTransform. After the transformation it should be a square again, scaled down from 336x336 to 168x168 (just an example). In the image above the height is correct but the width is too small.
It's strange because if I change the size of my custom Tile control to 200x200 pixels and use the same ScaleTransform with factor 0.5 it will be scaled down correctly.
Your GridCellSize is killing the transform. Make it bigger, try (400, 400). Also get rid of that StackPanel.
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Border BorderBrush="HotPink" BorderThickness="1,1,1,1" Tap="Border_Tap" HorizontalAlignment="Left" VerticalAlignment="Top">
<Custom:Tile RenderTransformOrigin="0,0" HorizontalAlignment="Left" VerticalAlignment="Top">
<Custom:Tile.RenderTransform>
<CompositeTransform ScaleX="0.5" ScaleY="0.5"/>
</Custom:Tile.RenderTransform>
</Custom:Tile>
</Border>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
I'm making a board game. To construct the board, I do the following:
// adapted from http://code.msdn.microsoft.com/windowsapps/Reversi-XAMLC-sample-board-816140fa/sourcecode?fileId=69011&pathId=706708707
// This is shit code.
async void PlayGame_Loaded(object sender, RoutedEventArgs e)
{
foreach (var row in Game.ALL_ROWS)
{
boardGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(BOARD_GRID_SIDE_LENGTH) });
}
var maxColIndex = Game.ALL_ROWS.Max();
foreach (var col in Enumerable.Range(0, maxColIndex))
{
boardGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(BOARD_GRID_SIDE_LENGTH) });
}
// ...
}
(Feel free to suggest alternate approaches.)
Basically, I create a bunch of rows and columns based on a pre-set height and width, and fill those rows and columns with board spaces. This works fine when I set the row and column lengths to fit my laptop, but obviously it won't work on devices with different resolutions. (For instance, it's truncated on the Surface RT.) How do I get around this? Can I specify a side length that's a portion of the parent container? What's the best practice here?
Your best bet would probably be to use a ViewBox, assuming the board you want to draw has a constant number of rows/columns.
I use this:
var bounds = Window.Current.Bounds;
double height = bounds.Height;
double width = bounds.Width;
to find out the screen resolution on which my apps are, and then re-size my grid items on function of the size of the screen.
EDIT:
So for your code I would do:
async void PlayGame_Loaded(object sender, RoutedEventArgs e)
{
var bounds = Window.Current.Bounds;
double BOARD_GRID_SIDE_LENGTH = bounds.Height;
double BOARD_GRID_SIDE_WIDTH = bounds.Width;
foreach (var row in Game.ALL_ROWS)
{
boardGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(BOARD_GRID_SIDE_LENGTH) });
}
var maxColIndex = Game.ALL_ROWS.Max();
foreach (var col in Enumerable.Range(0, maxColIndex))
{
boardGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(BOARD_GRID_SIDE_WIDTH) });
}
// ...
}
The sound solution is to divide your page so that the boardGrid takes 2/3 of the screen, the rest of your content (buttons, timer, score board...) in the other third.
Secondly, enclose it inside a Viewbox element so that the board dimensions are recalculated when you go to different view modes (Full, Portrait, Snapped, Filled)
Finally, make sure to make the page inherit LayoutAwarePage instead of Page so that it handles the changes of view modes gracefully
Here's a rough mockup:
<common:LayoutAwarePage
xmlns:UI="using:Microsoft.Advertising.WinRT.UI"
x:Class="ApexKT.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp"
xmlns:common="using:MyApp.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:snaps="http://schemas.microsoft.com/netfx/2007/xaml/presentation"
mc:Ignorable="d" >
<Grid x:Name="bigGrid">
<StackPanel Orientation="Horizontal">
<StackPanel x:Name="OneThird">
<TextBlock x:Name="ScoreBoard" Text="Score:" />
<Button x:Name="Button1" Content="Button 1" Click="" />
</StackPanel>
<Viewbox x:Name="TwoThirds">
<Viewbox.Transitions>
<TransitionCollection>
<ContentThemeTransition />
</TransitionCollection>
</Viewbox.Transitions>
<Grid x:Name="boardGrid"><!-- insert row and column definitions here, or hard code them --></Grid>
</Viewbox>
</StackPanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<VisualState x:Name="Snapped" />
<VisualState x:Name="FullScreenPortrait" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
`
When I run the Channel9 MSDN Kinect quickstart series code for skeletal tracking, there are suppose to be images/ellipses that overlap the location of the joints selected. Instead I get images/ellipses that are slightly off and not exactly over the location of the joints. No matter where I move the joints, the images stay off to the side of the exact location of the joint. I have seen the video here (http://channel9.msdn.com/Series/KinectQuickstart/Skeletal-Tracking-Fundamentals) and theirs works. Is there something I did wrong that I am not noticing that I changed or forgot to update?
Update:
This is some line of code that might be causing it:
CoordinateMapper cm = new CoordinateMapper(kinectSensorChooser1.Kinect);
//Map a joint location to a point on the depth map
//head
DepthImagePoint headDepthPoint =
cm.MapSkeletonPointToDepthPoint(first.Joints[JointType.Head].Position, depth.Format);
This is a change over what is in the video as they updated with a new method to map skeleton to depth and depth to color point. It eliminates an error that occurred when the stream was terminated.
The method has parameters that ask for position and format. Could it be that the depth.Format is not the same format as the RGB640x480Resolution?
//Map a depth point to a point on the color image
//head
ColorImagePoint headColorPoint =
cm.MapDepthPointToColorPoint(depth.Format, headDepthPoint,
ColorImageFormat.RgbResolution640x480Fps30);
On the main window screen this is what I have for creating the images i am using:
<Canvas Name="MainCanvas">
<my:KinectColorViewer Canvas.Left="50" Canvas.Top="50" Height="480" Name="kinectColorViewer1" Width="640"
Kinect="{Binding ElementName=kinectSensorChooser1, Path=Kinect}" />
<Ellipse Canvas.Left="0" Canvas.Top="0" Height="50" Name="leftEllipse" Width="50" Fill="#FF4D298D" Opacity="1" Stroke="White"></Ellipse>
<Ellipse Canvas.Left="100" Canvas.Top="0" Height="50" Name="rightEllipse" Stroke="White" Width="50" Fill="#FF2CACE3" Opacity="1" />
<my:KinectSensorChooser Canvas.Left="250" Canvas.Top="380" Name="kinectSensorChooser1" Width="328" />
<Ellipse Canvas.Left="225" Canvas.Top="84" Height="50" Name="headImage" Stroke="White" Width="50" Fill="#FF2CACE3" Opacity="1" />
<my:KinectSkeletonViewer Canvas.Left="646" Canvas.Top="240" Name="kinectSkeletonViewer1" Width="640" Height="480" Kinect="{Binding ElementName=kinectSensorChooser1, Path=Kinect}" />
</Canvas>
And I have commented these lines of code about scaling position
//ScalePosition(headImage, first.Joints[JointType.Head]);
// ScalePosition(leftEllipse, first.Joints[JointType.HandLeft]);
//ScalePosition(rightEllipse, first.Joints[JointType.HandRight]);
and this function has been commented.
private void ScalePosition(FrameworkElement element, Joint joint)
{
//convert the value to X/Y
//Joint scaledJoint = joint.ScaleTo(1280, 720);
//convert & scale (.3 = means 1/3 of joint distance)
// Joint scaledJoint = joint.ScaleTo(1280, 720, .3f, .3f);
// Canvas.SetLeft(element, scaledJoint.Position.X);
// Canvas.SetTop(element, scaledJoint.Position.Y);
}
and this is commented out so no transform parameters are used:
//sensor.SkeletonStream.Enable(parameters);
and this is what stream and skeleton enable lines of code look like:
sensor.SkeletonStream.Enable();
sensor.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(sensor_AllFramesReady);
sensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
Try the following steps.
Comment the lines that scaleTo() for testing porposes.
You initialized kinect with 640x480? Try to put this sizes to your my:KinectSkeletonViewer element on the XAML. I see that actualy it is Width="320" Height="240", change it.
Also check to not use strech on your Image element or in the Canvas that hosts it, for security, delete the Height="Auto" and Width="Auto" from your MainCanvas.
Start your skeleton stream with no TransformSmoothParameter or, if you want to use this, try the following parameters
this.KinectSensorManager.TransformSmoothParameters = new TransformSmoothParameters
{
Smoothing = 0.5f,
Correction = 0.5f,
Prediction = 0.5f,
JitterRadius = 0.05f,
MaxDeviationRadius = 0.04f
};
I am do the same you are trying to do using kinect for xbox and it worked fine to me.
When the channel9 video was recorded the sdk did not have the CoordinateMapper class, so, you dont need to convert to depth then to color, the coordinate mapper has a method to directly map the skeleton point to the color point, the method is MapSkeletonPointToColorPoint(), use the method and dont worry about the Depth Data.
It looks the problem I had was in the xaml code. I replaced the xaml code I had and replaced it with the original xaml code for the images and objects on my canvas.
<my:KinectColorViewer Canvas.Left="0" Canvas.Top="0" Width="640" Height="480" Name="kinectColorViewer1"
Kinect="{Binding ElementName=kinectSensorChooser1, Path=Kinect}" />
<Ellipse Canvas.Left="0" Canvas.Top="0" Height="50" Name="leftEllipse" Width="50" Fill="#FF4D298D" Opacity="1" Stroke="White" />
<Ellipse Canvas.Left="100" Canvas.Top="0" Fill="#FF2CACE3" Height="50" Name="rightEllipse" Width="50" Opacity="1" Stroke="White" />
<my:KinectSensorChooser Canvas.Left="250" Canvas.Top="380" Name="kinectSensorChooser1" Width="328" />
<Image Canvas.Left="66" Canvas.Top="90" Height="87" Name="headImage" Stretch="Fill" Width="84" Source="/SkeletalTracking;component/c4f-color.png" />
It might been have been a property or event setting that I had set incorrectly or not at all that introduced a shift in the image objects.
I have a little problem, I have a group item which I want to give a background image which it should scale keeping it's correct dimensions but by default it shows the image from the top left, I want the image to be centered.
Here is an illustration to further explain my problem. (the gray part is what is clipped)
And I have this XAML
<Image Source="/url/to/image.jpg" Stretch="UniformToFill"/>
I managed to solve my problem, I made the image larger than the container it was placed in and vertical aligned it in the center.
<Grid HorizontalAlignment="Left" Width="250" Height="125">
<Image Source="/url/to/image.jpg" Stretch="UniformToFill" Height="250" Margin="0" VerticalAlignment="Center"/>
</Grid>
The overflow of the image was invisible :)
In case the size of the source image is unknown in advance I had to use different trick:
<Border Width="250" Height="250">
<Border.Background>
<ImageBrush ImageSource="/url/to/image.jpg"
Stretch="UniformToFill"/>
</Border.Background>
</Border>
I wrote a behavior for Silverlight/Windows Phone that treats a similar situation. The picture I have to show may be larger or higher and I must display it in a square.
The behavior calculates the width/height ratio of both the container and the picture. Depending on which is the largest/highest, I resize the Image control to have this clipping effect with the parent control.
Here is a sample XAML to use with my behavior.
<Border Height="150" Width="150">
<Image Source="{Binding BitmapImage}" Stretch="UniformToFill"
HorizontalAlignment="Center" VerticalAlignment="Center">
<i:Interaction.Behaviors>
<b:FillParentBehavior />
</i:Interaction.Behaviors>
</Image>
</Border>
Here is an excerpt from the C# code. The full code can be found here: FillParentBehavior.cs
double width = this.AssociatedObject.Width;
double height = this.AssociatedObject.Height;
var parentSize = new Size(this.parent.ActualWidth, this.parent.ActualHeight);
var parentRatio = parentSize.Width / parentSize.Height;
// determine optimal size
if (this.AssociatedObject is Image)
{
var image = (Image)this.AssociatedObject;
if (image.Source is BitmapImage)
{
var bitmap = (BitmapImage)image.Source;
var imageSize = new Size(bitmap.PixelWidth, bitmap.PixelHeight);
var imageRatio = imageSize.Width / imageSize.Height;
if (parentRatio <= imageRatio)
{
// picture has a greater width than necessary
// use its height
width = double.NaN;
height = parentSize.Height;
}
else
{
// picture has a greater height than necessary
// use its width
width = parentSize.Width;
height = double.NaN;
}
}
}
this.AssociatedObject.Width = width;
this.AssociatedObject.Height = height;