Silverlight Canvas on Scrollviewer not triggering - wpf

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;
}

Related

What's the right way to create a XAML Border with repeating elements in Silverlight?

I'm trying to create a border with a blue background and repeating circles. As an example:
For the vertical portion, I'm using a vertical StackPanel in a Grid. A circle (overlapping a blue Rectangle) is declared in a ControlTemplate. To produce the repetition, I've copy-pasted a bunch of ContentControls, each of which points to my ControlTemplate.
For example:
<StackPanel
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="0"
Orientation="Vertical"
>
<ContentControl
attachedProperties:LightEllipseAttachedProperties.LightState="{Binding ElementName=PhoneApplicationPage, Path=GameController.Instance.Lights}"
Template="{StaticResource LightbulbTemplate}"
/>
**Repeat N times**
<ContentControl
attachedProperties:LightEllipseAttachedProperties.LightState="{Binding ElementName=PhoneApplicationPage, Path=GameController.Instance.Lights}"
Template="{StaticResource LightbulbTemplate}"
/>
</StackPanel>
<ControlTemplate
x:Key="LightbulbTemplate"
>
<Grid>
<VisualStateManager.VisualStateGroups>
</VisualStateManager.VisualStateGroups>
<Rectangle
Fill="#3300CC"
Height="15"
Width="15"
/>
<Ellipse
x:Name="LightEllipse"
Height="8"
Width="8"
>
<Ellipse.Fill>
<SolidColorBrush />
</Ellipse.Fill>
</Ellipse>
</Grid>
</ControlTemplate>
My question: Is there a better way to create a border of repeating elements using Silverlight? Perhaps Border has a Tiling capability so it will repeat the ControlTemplate itself, rather than me adding individual ContentControls?
If you want a simple method for building a shape like that, you could try using a Rectangle with a custom StrokeDashArray:
It was generated by this XAML code:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Height="200">
<Rectangle StrokeThickness="14" StrokeDashCap="Round" Stroke="#FF00B2E6" />
<Rectangle StrokeDashArray="0.1 1.3"
StrokeThickness="10" StrokeDashCap="Round" Margin=".9" >
<Rectangle.Stroke>
<SolidColorBrush Color="#BFFF0606"/>
</Rectangle.Stroke>
</Rectangle>
</Grid>
The XAML uses 2 rectangles. One as the "background" color and the other for the circles. By experimenting with the StrokeDashArray values, you can control the shape of the dash and the distance to the next dash. By using a Round dash cap, and a small dash size (.1), it generates a shape that looks nearly round. You can experiment with the location of the Rectangles, Margins etc., to control the final look.
The nice part about using this technique is that it's extremely an efficient operation on the Phone to draw the shape and it will automatically resize to content as needed.
I've a slightly different proposal (not only in XAML):
Make Background your Border. I've managed to do something like this:
It works quite good and automatically fits to UIElement size, thought may need some time (not much) to load (but can be prepared when you start your App and then reused). I've done it via WritableBitmap - just rendered that many elements (the adventage is that any elements may be used - stars, tringles, even other images) that I need:
private WriteableBitmap CreateBorderBrush(int width, int height)
{
Rectangle firstBrush = new Rectangle();
firstBrush.Width = 15;
firstBrush.Height = 15;
firstBrush.Fill = new SolidColorBrush(Colors.Blue);
Ellipse secondBrush = new Ellipse();
secondBrush.Width = 8;
secondBrush.Height = 8;
secondBrush.Fill = new SolidColorBrush(Colors.Orange);
int dimensionX = width - width % 15;
int dimensionY = height - height % 15;
WriteableBitmap bitmapToBrush = new WriteableBitmap(dimensionX, dimensionY);
for (int i = 0; i < width / 15; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = i * 15, Y = 0 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = i * 15 + 3, Y = 3 });
}
for (int i = 1; i < height / 15 - 1; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = 0, Y = i * 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = 3, Y = i * 15 + 3 });
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = dimensionX - 15, Y = i * 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = dimensionX - 15 + 3, Y = i * 15 + 3 });
}
for (int i = 0; i < width / 15; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = i * 15, Y = dimensionY - 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = i * 15 + 3, Y = dimensionY - 15 + 3 });
}
bitmapToBrush.Invalidate();
return bitmapToBrush;
}
In MainPage constructor I've used it like this:
this.Loaded += (s, e) =>
{
myGrid.Background = new ImageBrush() { ImageSource = CreateBorderBrush((int)myGrid.ActualWidth, (int)myGrid.ActualHeight) };
};
And the XAML code:
<Grid Name="myGrid" Grid.Row="0" Width="300" Height="200">
<Button x:Name="first" Content="Button" Width="150" Height="100"/>
</Grid>
I would suggest creating a separate UserControl. The number of circles can be a Dependencyproperty of the UserControl. You can then use a ItemsControl to repeat the circles.

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.

Click, Drag, and Scroll Canvas View

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;
}

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.

Resources