Click, Drag, and Scroll Canvas View - wpf

I have a ListBox with a ItemsPanelTemplate of Canvas. I know the ScrollViewer will not work with the Canvas unless its given a height and width. I DO NOT want to give the canvas a height and width because it will not always be constant. Is there any other work around or tricks anyone has gotten to work for this situation. I know I can't be the only one with this problem. Thanks in advance here is my code so far.
Another problem is I am unable to place the ScrollViewer inside the ItemsPanelTemplate because it can only have one element nested inside.
This also restricts me from placing the canvas inside a grid to get positioning.
XAML:
<!--Core Viewer-->
<ScrollViewer x:Name="scrollViewer"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Hidden">
<ListBox x:Name="objCoreViewer"
ItemsSource="{Binding ItemsSource}"
Background="LightGray"
SelectionChanged="objCoreViewer_SelectionChanged"
ItemTemplateSelector="{DynamicResource CoreViewerDataTemplateSelector}"
ItemContainerStyleSelector="{DynamicResource ItemContainerStyleSelector}"
PreviewMouseWheel="objCoreViewer_PreviewMouseWheel">
<!-- Core Map Canvas -->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="objCoreViewerCanvas"
Background="Transparent">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding Path=Value, ElementName=ZoomSlider}"
ScaleY="{Binding Path=Value, ElementName=ZoomSlider}" />
</Canvas.LayoutTransform>
</Canvas>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</ScrollViewer>

To get the canvas to grow based on the the child elements inside of it you will need to override the Canvas's MeasureOverride event in a custom class that inherits from Canavas. This code works great for me:
protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
{
Size toReport = new Size();
foreach (UIElement element in this.InternalChildren)
{
//Get the left most and top most point. No using Bottom or Right in case the controls actual bottom and right most points are less then the desired height/width
var left = Canvas.GetLeft(element);
var top = Canvas.GetTop(element);
left = double.IsNaN(left) ? 0 : left;
top = double.IsNaN(top) ? 0 : top;
element.Measure(constraint);
Size desiredSize = element.DesiredSize;
if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
{
//left += desiredSize.Width;
//top += desiredSize.Height;
toReport.Width = toReport.Width > left +desiredSize.Width ? toReport.Width : left + desiredSize.Width;
toReport.Height = toReport.Height > top+desiredSize.Height ? toReport.Height : top + desiredSize.Height;
}
}
//Make sure scroll includes the margins incase of a border or something
toReport.Width += this.Margin.Right;
toReport.Height += this.Margin.Bottom;
return toReport;
}

Related

Why LayoutTransform do not change vertice coordinates (x1,y1) on rotation

I have a canvas which contains shapes (path) where i bind angle with rotatetransform in layout transform.
I generate MouseRightButtonUp on canvas when mouse is over the shape (generated by my path) then right click i rotate by 90 degree using Layout transform.
But it behaves like Render transform because the my binded canvas.left and canvas.top do not updates/change after rotation on right click of mouse over shape
whereas angle gets changed.
my code is here :
<Canvas Name="OuterCanvas" Background="Green" Height="700" Width="1000" >
<ItemsControl AllowDrop="True" x:Name="itemsCtrl" ItemsSource="{Binding ShapesCollection,Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas ClipToBounds="True" IsItemsHost="True" Height="700" Width="1000" Background="Transparent" x:Name="dragCanvas">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<ie:CallMethodAction MethodName="MouseRightButtonUpEventHandler" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path x:Name="im" Canvas.Left="{Binding Path=Canvas_Left , Mode=TwoWay}"
Canvas.Top="{Binding Path=Canvas_Top , Mode=TwoWay}"
Fill="{Binding Fill}" Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"
Data="{Binding Path=Data,Mode=TwoWay}">
<Path.LayoutTransform>
<RotateTransform Angle="{Binding Path=Angle , Mode=TwoWay}"/>
</Path.LayoutTransform>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=Canvas_Left , Mode=TwoWay}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Canvas_Top , Mode=TwoWay}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
the invoked method for rotation in viewmodel is:
public void MouseRightButtonUpEventHandler(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
ElementBeingDragged = e.Source as UIElement;
if (ElementBeingDragged is System.Windows.Shapes.Path)
if (ElementBeingDragged != null)
{
if (ElementBeingDragged.AllowDrop)
{
var rotateTransform = ((System.Windows.Shapes.Path)ElementBeingDragged).LayoutTransform as RotateTransform;
if (rotateTransform == null)
{
(e.Source as FrameworkElement).LayoutTransform = new RotateTransform();
}
((System.Windows.Shapes.Path)ElementBeingDragged).LayoutTransform = rotateTransform;
((System.Windows.Shapes.Path)ElementBeingDragged).RenderTransformOrigin = new Point(0.5, 0.5);
rotateTransform.Angle += 90;
}
((System.Windows.Shapes.Path)ElementBeingDragged).UpdateLayout();//.LayoutUpdated();
isDragInProgress = false;
Mouse.Capture(null);
}
}
Why Canvas.Left and Canvas.Top never gets updated even after the change in the position of shapeon change of the angle?
(lets say rectangle has A(x1,y1) B(x2,y2) , c(x3,y3) D(x4,y4) then after 90 degree rotation it must be A(x2,y2) B(x3,y3) , c(x4,y4) D(x1,y1)
why the in my Model below Canvas.Left and Canvas.Top never updates ?
public class Shapes :ICloneable
{ private double angle;
public double Angle
{
get { return angle; }//Updates on rotation
set
{
angle = value;
RaisePropertyChanged("Angle");
}
}
public double canvas_Top { get; set; }
public double Canvas_Top
{
get { return canvas_Top; }//Never updates on rotation
set
{
canvas_Top = value;
RaisePropertyChanged("Canvas_Top");
}
}
private double canvas_Left;
public double Canvas_Left
{
get { return canvas_Left; } //Never updates on rotation
set
{
canvas_Left = value;
RaisePropertyChanged("Canvas_Left");
}
}
}
This behavior is expected in Rendertransform but why in this case LayoutTransform behave like Rendertransform because i have used LayOutTranform
A transform does not change the underlying coordinates or Control Properties. Think of it like applying a mask over the base Properties that calculates new positions or values but does not change the original.
For example: If i have a square with a side length of 2. I then apply a Scaling Tranform of 2. The original side length remains =2. The transform will calculate a new side length of 4 and display the square at a size of 4. If i remove the transform, the square would be the original size of 2.
To Compare to your 90 degree rotation example, While you are correct that the rendered values of x1,y1 and x2,y2 change, the underlying values do not. I.e. Visually you see them change, but the transform is what makes that visual change.
The difference between RenderTransform .vs. LayoutTransform primarily happens to WHEN these calculations happen (i.e. when do i multiply my square sides by 2, or rotate my rectangle by 90 degrees). Both will still not adjust the origin of your other properties. Look Here for a good explanation.
(Im sure there a more differences; but this is where i believe the confusion is happening)
If you want to actually adjust the Canvas.Top or Canvas.Left properties from a transform, you would need to apply the transform to those values and then set the Properties with the calculated value from your Transform.

Dynamically adding and moving ellipses using WPF

I'm using WPF and having trouble dynamically/programatically adding ellipses to my grid.
I'm dynamically allocating and placing ellipses inside myGrid. Trouble is the position on the ellipses don't change. I'm using Canvas.SetLeft and SetTop, but the ellipses still seem stuck.
Here is the code for dynamic allocation :
{
...
Ellipse el = new Ellipse();
RadialGradientBrush b = new RadialGradientBrush();
b.RadiusX = r * 10.0f;
b.RadiusY = r * 10.0f;
b.GradientOrigin = new Point(0.5f, 0.5f);
b.GradientOrigin = new Point(0.5f, 0.5f);
b.GradientStops.Add(new GradientStop(Colors.Green, 0.0));
b.GradientStops.Add(new GradientStop(Colors.Blue, 1.0));
el.Width = 5.0f + r * 20.0f;
el.Height = 5.0f + r * 20.0f;
el.Stroke = b;
SetEllipsePosition(el, p);
this.myGrid.Children.Add(el);
...
}
private void SetEllipsePosition(FrameworkElement ellipse, Point j)
{
Canvas.SetLeft(ellipse, j.X);
Canvas.SetTop(ellipse, j.Y);
}
<Grid Height="480" Name="myGrid" Width="640">
<GroupBox Header="Pattern" Height="117" HorizontalAlignment="Left" Margin="10,564,0,0" Name="groupBox1" VerticalAlignment="Top" Width="238"></GroupBox>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="33,30,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click_1" />
<Grid Margin="6,507,408,-121">
<RadioButton Content="Lines" Height="16" HorizontalAlignment="Left" Margin="15,18,0,0" Name="rbLines" VerticalAlignment="Top" GroupName="RenderStyles" />
<RadioButton Content="Circles" Height="16" HorizontalAlignment="Left" Margin="15,49,0,0" Name="rbCircles" VerticalAlignment="Top" GroupName="RenderStyles" />
</Grid>
</Grid>
The problem is that you are using a Grid but setting Canvas properties, you could add a Canvas into the Grid and draw you ellipses on the Canvas ( add them to the Canvases children), and then it would work.
Or you could use the Margin property of your ellipse to set it's position on the Grid
Canvas.Left and Canvas.Top are attached properties: you set them only if your UI element is going to be contained in a Canvas; and only when it is on a Canvas will those properties be used (by the Canvas layout manager). Same with attached properties from Grid (such as Grid.Column to tell the Grid parent in what column the UI elements "wants" to be), Panel (Panel.ZIndex to tell the Panel parent at what z index the UI element should be put), etc.

Silverlight 4 - Am I using TransformToVisual() correctly?

I have just started to play around with Silverlight and I decided to do a small app in Visual Studio 2010. I am trying to find the current position of a usercontrol in a Canvas. Here is the XAML layout:
<Grid x:Name="LayoutRoot" Background="#FF141313">
<Grid.RowDefinitions>
<RowDefinition Height="39"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Opacity="0.5" Background="{x:Null}" BorderThickness="1" FontFamily="Courier New" Content="Align Images" Cursor="Hand" Name="buttonAlignImages" Click="buttonAlignImages_Click" Margin="45,8,0,11" HorizontalAlignment="Left" Width="84" />
<Button HorizontalAlignment="Left" Width="33" Opacity="0.5" Background="{x:Null}" BorderThickness="1" FontFamily="Courier New" Content="Home" Cursor="Hand" Margin="8,8,0,11"/>
<Canvas x:Name="ImageContainer" Margin="8" Grid.Row="1" Background="Black"/>
</Grid>
My usercontrol is added to the "ImageContainer" Canvas. One of the buttons in XAML is called "buttonAlignImages". When the user clicks this I basically want the images to be aligned in a specific manner. Anyways to do this I want to first get the position of the usercontrol embedded in the "ImageContainer". So here is the code when the button is clicked:
private void buttonAlignImages_Click(object sender, RoutedEventArgs e)
{
double margin = 5.0;
Point top_left = new Point(margin, margin);
Point top_right = new Point(ActualWidth - margin, margin);
Point bottom_left = new Point(5.0, ActualHeight - margin);
Point bottom_right = new Point(ActualWidth - margin, ActualHeight - margin);
foreach (UIElement element in ImageContainer.Children)
{
Photo singlePhoto = element as Photo;
if (singlePhoto != null)
{
// get the transform for the current photo as applicable to basically this visual
GeneralTransform gt = singlePhoto.TransformToVisual(ImageContainer);
// get the position on the root visual by applying the transform to the singlePhoto
Point singlePhotoTopLeft = gt.Transform(new Point(0, 0));
// now translate the position of the singlePhoto
singlePhoto.Translate(singlePhotoTopLeft.X - top_left.X, singlePhotoTopLeft.Y - top_left.Y);
}
}
}
public void Translate(double deltaX, double deltaY)
{
translateTransform.X += deltaX;
translateTransform.Y += deltaY;
}
The embedded photo usercontrol does move around but when I call gt.Transform(new Point(0,0)) it always gives me (0,0), so the resulting translation is only by 5 pixels. Why does this happen? Am I not using TransformToVisual() correctly?
It s been a while but if u cannot live on without the answer :) will u try
Point singlePhotoTopLeft = gt.Transform(new Point());
instead ?

WPF: how to make the (0,0) in center inside a Canvas

The WPF Canvas has a coordinate system starting at (0,0) at the top-left of the control.
For example, setting the following will make my control appear on the top-left:
<Control Canvas.Left="0" Canvas.Top="0">
How can I change it to the standard cartesian coordinates?
Basically:
(0,0) at center
flip Y
I noticed this post is similar, but it does not talk about translating the coordinate system. I tried adding a TranslateTransform, but I can't make it work.
There is no need to create a custom Panel. Canvas will do just fine. Simply wrap it inside another control (such as a border), center it, give it zero size, and flip it with a RenderTransform:
<Border>
<Canvas HorizontalAlignment="Center" VerticalAlignment="Center"
Width="0" Height="0"
RenderTransform="1 0 0 -1 0 0">
...
</Canvas>
</Border>
You can do this and everything in the canvas will still appear, except (0,0) will be at the center of the containing control (in this case, the center of the Border) and +Y will be up instead of down.
Again, there is no need to create a custom panel for this.
It was very easy to do. I looked at the original Canvas's code using .NET Reflector, and noticed the implementation is actually very simple. The only thing required was to override the function ArrangeOverride(...)
public class CartesianCanvas : Canvas
{
public CartesianCanvas()
{
LayoutTransform = new ScaleTransform() { ScaleX = 1, ScaleY = -1 };
}
protected override Size ArrangeOverride( Size arrangeSize )
{
Point middle = new Point( arrangeSize.Width / 2, arrangeSize.Height / 2 );
foreach( UIElement element in base.InternalChildren )
{
if( element == null )
{
continue;
}
double x = 0.0;
double y = 0.0;
double left = GetLeft( element );
if( !double.IsNaN( left ) )
{
x = left;
}
double top = GetTop( element );
if( !double.IsNaN( top ) )
{
y = top;
}
element.Arrange( new Rect( new Point( middle.X + x, middle.Y + y ), element.DesiredSize ) );
}
return arrangeSize;
}
}
You can simply change the Origin with RenderTransformOrigin.
<Canvas Width="Auto" Height="Auto"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1" ScaleX="1" />
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
The best thing is to write a custom Canvas, in which you can write ArrangeOverride in such a way that it takes 0,0 as the center.
Update : I had given another comment in the below answer (#decasteljau) I wont recommend deriving from Canvas, You can derive from Panel and add two Attached Dependancy properties Top and Left and put the same code you pasted above. Also doesnt need a constructor with LayoutTransform in it, And dont use any transform on the panel code use proper measure and arrange based on the DesiredSize of the panel So that you can get nice content resize behavior too. Canvas doesn't dynamically position items when the Canvas size changes.

Silverlight Canvas on Scrollviewer not triggering

Why does this work so well in wpf
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="MyDesigner">
</Canvas>
</ScrollViewer>
Now when I do the same thing in silverlight and load an control "that can be dragged" the scrollbars are not triggered, when I drag out of view, nothing happens... but in wpf it automatically shows them...
As a quick check against the normal 'gotcha' have you explicitly set the canvas height / width properties?
If I knock up some xaml for test purposes and run it:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="test" Background="Beige">
<TextBlock Canvas.Left="2000" Canvas.Top="200" Text="test"/>
</Canvas>
</ScrollViewer>
Will not show a scroll bar, even though I have explicitly created content in the canvas 2000 to the left, the canvas width not being set means the scroll viewer has no range to bind to so to speak. The canvas without a width is considered is just infinitely wide from what I can see. Whilst this is not the same as dragging the concept of putting a piece of content outside of the current view is there.
As soon as you add a width, it defines a finite area to scroll on and the scroll bar shows up.
Here is the solution I found.
The canvas can grow dynamically, but you will need to explicitly set the height to a new value.
So, if you have 20 textblocks with a height of 21, you need to set the canvas height as:
Canvas.Height = 22.0 * 100;
for the scrollviewer to pick up the new height.
MainPage.xaml
<Canvas Canvas.Left="5" Canvas.Top="25" >
<ScrollViewer Width="300" Height="700" x:Name="CanSummaryScroller">
<Canvas x:Name="canSummaryCells" >
</Canvas>
</ScrollViewer>
</Canvas>
MainPage.xaml.cs
boxDevSumList is a List of TextBoxes
for (int i = 0; i < 100; i++)
{
boxDevSumList.Add(new Cell());
(boxDevSumList.ElementAt(i)).Width = 271;
(boxDevSumList.ElementAt(i)).Height = 21;
(boxDevSumList.ElementAt(i)).SetValue(FontFamilyProperty, new FontFamily("Calibri"));
(boxDevSumList.ElementAt(i)).FontSize = 12;
(boxDevSumList.ElementAt(i)).Text = "this is a test";
if (i.Equals(1) || i.Equals(3) || i.Equals(5) || i.Equals(7) || i.Equals(9))
(boxDevSumList.ElementAt(i)).Background = lgBrush;
(boxDevSumList.ElementAt(i)).Opacity = .9;
canSummaryCells.Children.Add(boxDevSumList.ElementAt(i));
boxDevSumList.ElementAt(i).SetValue(Canvas.TopProperty, (double)(i * 21) + 45);
boxDevSumList.ElementAt(i).SetValue(Canvas.LeftProperty, 4.0);
canSummaryCells.Height = 22.0 * 100;
}

Resources