I have a canvas (not InkCanvas!) and I am able to draw Polylines on it. This is working just fine but there is a huge problem with drawing out of bounds like shown in the GIF below.
My canvas is inside a ScrollViewer and the ScrollViewer is inside a GridView.
I tried to catch the pointer leaving the canvas with the following event handlers:
canvas.PointerExited += Canvas_PointerExited;
canvas.PointerCaptureLost += Canvas_PointerCaptureLost;
But it seems those events are fired way too slow.
I tried to use the Clip property of my canvas but there is no change in behaviour. And there is no "ClipToBound" property for the UWP canvas.
My whole view is generated in Code-Behind because I have to generate multiple canvases on one view.
Is there a way to stop this behaviour?
EDIT1:
As requested: more insight of my code.
The XAML Page looks like this:
<Grid x:Name="BoundingGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="15*"/>
</Grid.RowDefinitions>
<Grid x:Name="InkGrid" VerticalAlignment="Top" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
<Grid x:Name="CanvasGrid" Grid.Row="1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" VerticalAlignment="Top"/>
</Grid>
It's all inside a Page.
My code behind looks like this:
My constructors:
public ImprovedCanvasManager(Grid boundingGrid, Grid overviewGrid, string filepath, double height)
{
drawCanvas = new Canvas();
overviewGrid.Loaded += OverviewGrid_Loaded;
overviewGrid.SizeChanged += OverviewGrid_SizeChanged;
RowDefinition rd = new RowDefinition();
rd.Height = new GridLength(height);
overviewGrid.RowDefinitions.Add(rd);
InitializeScrollViewer();
Grid.SetRow(scroll, overviewGrid.RowDefinitions.Count);
Grid.SetColumn(scroll, 0);
scroll.Content = drawCanvas;
overviewGrid.Children.Add(scroll);
LoadImage(filepath);
}
public ImprovedCanvasManager(Grid boundingGrid, Grid overviewGrid, Grid inkToolGrid, string filepath, double height = 1000) : this(boundingGrid, overviewGrid, filepath, height)
{
AddDrawingToolsToCanvas(inkToolGrid, overviewGrid);
EnableDrawingOnCanvas(drawCanvas);
}
I only got two contructors to make it simple for me to instantiate canvases with the ability to draw and without the ability to draw.
This is how i initialise my ScrollViewer:
private void InitializeScrollViewer()
{
scroll = new ScrollViewer();
scroll.VerticalAlignment = VerticalAlignment.Top;
scroll.VerticalScrollMode = ScrollMode.Auto;
scroll.HorizontalScrollMode = ScrollMode.Auto;
scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
scroll.HorizontalScrollBarVisibility = ScrollBarVisibility.Visible;
scroll.ZoomMode = ZoomMode.Enabled;
scroll.ManipulationMode = ManipulationModes.All;
scroll.MinZoomFactor = 1;
scroll.MaxZoomFactor = 3;
}
Those are the only lines of code that affect any viewbuilding.
Edit 2:
My canvas doesn't fill the surrounding Grid on the left, but on the bottom.
The code in your PointerMoved handler should be relative to the canvas.
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
var point = e.GetCurrentPoint(canvas); // <-- relative to canvas.
var x = point.Position.X;
var y = point.Position.Y;
x = Math.Max(x, 0);
y = Math.Max(y, 0);
x = Math.Min(canvas.ActualWidth, x);
y = Math.Min(canvas.ActualHeight, y);
// add point to polyline...
}
If x/y are negative or greater than the size of the canvas, then they are out of bounds. You can either limit the point to the border of the canvas as the code above does, or you can discard the point completely.
Related
I want to get the Grid width from my page, so I tried this:
public MemeBuilder()
{
InitializeComponent();
ColumnWidth = (MainGrid.Width - (8 * 5)) / 7;
....
But MainGrid.Width returns -1.
Here is the xaml of the Grid:
<Grid x:Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
.....
What am I doing wrong?
You can subscribe to the SizeChanged event and from there get the Width:
private void MainGrid_SizeChanged(object sender, EventArgs e)
{
var grid = (Grid)sender;
var width = grid.Width;
}
My mistake, you are using xamarin form.I think you are not getting the width because of the lifecycle like what #Peter B said.
Either OnStart Event
protected override void OnStart()
{
ColumnWidth = (MainGrid.width- (8 * 5)) / 7;
}
or
Page Appearing Event if your grid is in another page
protected async override void OnAppearing()
{
if(!isLoaded)
{
//Do API Calls
isLoaded=true;
}
}
The best way to handle this is by using Xamarin.Essentials Device Display Information API
// Get Metrics
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
// Orientation (Landscape, Portrait, Square, Unknown)
var orientation = mainDisplayInfo.Orientation;
// Rotation (0, 90, 180, 270)
var rotation = mainDisplayInfo.Rotation;
// Width (in pixels)
var width = mainDisplayInfo.Width;
// Height (in pixels)
var height = mainDisplayInfo.Height;
// Screen density
var density = mainDisplayInfo.Density;
Now you can get the width by dividing the density something like:
var width = DeviceDisplay.MainDisplayInfo.Width / DeviceDisplay.MainDisplayInfo.Density;
Goodluck
Revert if you have questions
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.
Simple xaml:
<WrapPanel Orientation="Vertical">
<Ellipse Width="100" Height="100" Fill="Red" />
<Ellipse Width="100" Height="100" Fill="Yellow" />
<Ellipse Width="100" Height="100" Fill="Green" />
</WrapPanel>
Resizing window:
How to show vertical and horizontal scrollbars when content doesn't fit?
Note: this should work for any content.
I tried to put it into ScrollViewer:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<WrapPanel Orientation="Vertical">
...
</WrapPanel>
</ScrollViewer>
but then WrapPanel stops wrapping anything (always one column):
The problem here is that ScrollViewer gives (NaN, NaN) size to it child, so wrapping never occurs.
I tried to fix it by binding scroll viewer available height to max height of panel:
<ScrollViewer ...>
<WrapPanel MaxHeight="{Binding ViewportHeight, RelativeSource={RelativeSource AncestorType=ScrollViewer}}" ...>
This will limit panel height (not NaN anymore), so wrapping now occurs. But because this also adjust the height of panel - the vertical scrollbar will never appears:
How to add vertical scrollbar?
In my case WrapPanel is vertical, means it will fill columns as much as it can and then wrap to a new column from left to right. Scrollbars are needed when children doesn't fit either vertically (when available space is less than children height) or horizontally.
The idea thought can be used for a standard (horizontal) WrapPanel: from left to right, creating new rows when full. Absolutely same problem will arise (just tried it).
That sort of behavior is not possible with a WrapPanel without setting explicitly its Height/MinHeight for a Vertical orientation or Width/MinWidth for a Horizontal orientation. The ScrollViewer will only show the scrollbars when the FrameworkElement this scroll viewer wraps doesn't fit into the viewport.
You can create your own wrap panel that calculates its minimum size based on its children.
Alternatively, you can implement a Behavior<WrapPanel> or an attached property. This won't be as easy as just adding a couple of XAML tags, as you might expect.
We have solved this issue with an attached property. Let me give you an idea of what we did.
static class ScrollableWrapPanel
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ScrollableWrapPanel), new PropertyMetadata(false, IsEnabledChanged));
// DP Get/Set static methods omitted
static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var panel = (WrapPanel)d;
if (!panel.IsInitialized)
{
panel.Initialized += PanelInitialized;
}
// Add here the IsEnabled == false logic, if you wish
}
static void PanelInitialized(object sender, EventArgs e)
{
var panel = (WrapPanel)sender;
// Monitor the Orientation property.
// There is no OrientationChanged event, so we use the DP tools.
DependencyPropertyDescriptor.FromProperty(
WrapPanel.OrientationProperty,
typeof(WrapPanel))
.AddValueChanged(panel, OrientationChanged);
panel.Unloaded += PanelUnloaded;
// Sets up our custom behavior for the first time
OrientationChanged(panel, EventArgs.Empty);
}
static void OrientationChanged(object sender, EventArgs e)
{
var panel = (WrapPanel)sender;
if (panel.Orientation == Orientation.Vertical)
{
// We might have set it for the Horizontal orientation
BindingOperations.ClearBinding(panel, WrapPanel.MinWidthProperty);
// This multi-binding monitors the heights of the children
// and returns the maximum height.
var converter = new MaxValueConverter();
var minHeightBiding = new MultiBinding { Converter = converter };
foreach (var child in panel.Children.OfType<FrameworkElement>())
{
minHeightBiding.Bindings.Add(new Binding("ActualHeight") { Mode = BindingMode.OneWay, Source = child });
}
BindingOperations.SetBinding(panel, WrapPanel.MinHeightProperty, minHeightBiding);
// We might have set it for the Horizontal orientation
BindingOperations.ClearBinding(panel, WrapPanel.WidthProperty);
// We have to define the wrap panel's height for the vertical orientation
var binding = new Binding("ViewportHeight")
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(ScrollViewer)}
};
BindingOperations.SetBinding(panel, WrapPanel.HeightProperty, binding);
}
else
{
// The "transposed" case for the horizontal wrap panel
}
}
static void PanelUnloaded(object sender, RoutedEventArgs e)
{
var panel = (WrapPanel)sender;
panel.Unloaded -= PanelUnloaded;
// This is really important to prevent the memory leaks.
DependencyPropertyDescriptor.FromProperty(WrapPanel.OrientationProperty, typeof(WrapPanel))
.RemoveValueChanged(panel, OrientationChanged);
}
private class MaxValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Cast<double>().Max();
}
// ConvertBack omitted
}
}
It is maybe not the easiest way, and there are a little bit more lines that just a few XAML tags, but it works flawlessly.
You have to be careful with the error handling though. I've just omitted all the checks and exception handling in the sample code.
The usage is simple:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<WrapPanel Orientation="Vertical" local:ScrollableWrapPanel.IsEnabled="True">
<!-- Content -->
</WrapPanel>
</ScrollViewer
It seems monitoring for children is one of important task to achieve wanted. So why not creating custom panel:
public class ColumnPanel : Panel
{
public double ViewportHeight
{
get { return (double)GetValue(ViewportHeightProperty); }
set { SetValue(ViewportHeightProperty, value); }
}
public static readonly DependencyProperty ViewportHeightProperty =
DependencyProperty.Register("ViewportHeight", typeof(double), typeof(ColumnPanel),
new FrameworkPropertyMetadata(double.PositiveInfinity, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
{
var location = new Point(0, 0);
var size = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (location.Y != 0 && ViewportHeight < location.Y + child.DesiredSize.Height)
{
location.X = size.Width;
location.Y = 0;
}
if (size.Width < location.X + child.DesiredSize.Width)
size.Width = location.X + child.DesiredSize.Width;
if (size.Height < location.Y + child.DesiredSize.Height)
size.Height = location.Y + child.DesiredSize.Height;
location.Offset(0, child.DesiredSize.Height);
}
return size;
}
protected override Size ArrangeOverride(Size finalSize)
{
var location = new Point(0, 0);
var size = new Size(0, 0);
foreach (UIElement child in Children)
{
if (location.Y != 0 && ViewportHeight < location.Y + child.DesiredSize.Height)
{
location.X = size.Width;
location.Y = 0;
}
child.Arrange(new Rect(location, child.DesiredSize));
if (size.Width < location.X + child.DesiredSize.Width)
size.Width = location.X + child.DesiredSize.Width;
if (size.Height < location.Y + child.DesiredSize.Height)
size.Height = location.Y + child.DesiredSize.Height;
location.Offset(0, child.DesiredSize.Height);
}
return size;
}
}
The usage (instead of WrapPanel) is following:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<local:ColumnPanel ViewportHeight="{Binding ViewportHeight, RelativeSource={RelativeSource AncestorType=ScrollViewer}}" ... >
...
</local:ColumnPanel>
</ScrollViewer>
The idea is to calculate layout manually, while MeasureOverride and ArrangeOverride will be automatically called whenever children are changed: added, deleted, resized, etc.
Measure logic is simple: start from (0,0) and measure next child size, if it fits into current column - add it, otherwise start and new column by offsetting location. During whole measurement cycle adjust the resulting size.
The only missing part of puzzle is to provide into measure/arrange cycles ViewportHeight from parent ScrollViewer. This is the role of ColumnPanel.ViewportHeight.
Here is the demo (button add purple circle):
You can do this by wrapping your wrappanel in a scrollviewer, but then binding the height and width of the inner panel to the Height and Width of the Viewport of the scrollviewer, so it stretches and contracts with the rest of the screen.
I've also added minimum Height and Width to my sample, which ensures that the scrollbars appear once the wrap panel is pushed smaller than it's min dimensions
<ScrollViewer x:Name="sv" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<WrapPanel MinWidth="200" Width="{Binding ElementName=sv, Path=ViewportWidth}" MinHeight="200" Height="{Binding ElementName=sv, Path=ViewportHeight}">
<Ellipse Fill="Red" Height="200" Width="200"/>
<Ellipse Fill="Yellow" Height="200" Width="200"/>
<Ellipse Fill="Green" Height="200" Width="200"/>
</WrapPanel>
</ScrollViewer>
I've got a simple demo application that uses an image as the background of an InkCanvas and I scale the strokes when the display of the image is resized so that they remain in the same place relative to the image. Since you can draw -> resize -> draw -> resize -> draw this means I have to scale each stroke a different amount each time by assigning the PointTransform on each stroke.
float thisScale = (float)(scale / _prevScale);
foreach (InkStroke stroke in myCanvas.InkPresenter.StrokeContainer.GetStrokes())
{
float thisPointScale = thisScale * stroke.PointTransform.M11;
stroke.PointTransform = Matrix3x2.CreateScale(new Vector2(thisPointScale));
}
This resizes the length of the strokes perfectly well. However, it does nothing to the thickness of the strokes. This is even more evident when you use a thick or non-uniform pen (eg the highlighter pen).
These link to two screen clips which show the results.
Full-screen - https://1drv.ms/i/s!ArHMZAt1svlBiZZDfrxFqyGU1bJ6MQ
Smaller window - https://1drv.ms/i/s!ArHMZAt1svlBiZZCqHHYaISPfWMMpQ
Any ideas on how I can resize the thickness of the strokes?
Apply a ScaleTransform to the InkCanvas control. That'll take care of scaling the ink stroke,the stroke locations and the background image. Essentially the transform applies to everything contained in the InkCanvas. No need to use the Matrix with the StrokeCollection.
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="Red Highlighter "
x:Name="InkRedAttributesButton"
Click="InkRedAttributesButton_Click" />
<Button Content="Blue Highlighter "
x:Name="InkBlueAttributesButton"
Click="InkBlueAttributesButton_Click" />
<Button Content="Scale Down"
x:Name="ScaleDownButton"
Click="ScaleDownButton_Click" />
<Button Content="Scale Up"
x:Name="ScaleUpButton"
Click="ScaleUpButton_Click" />
</StackPanel>
<InkCanvas x:Name="myCanvas"
Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top">
<InkCanvas.Background>
<ImageBrush ImageSource="/SO_Questions;component/Images/Star02.jpg"
Stretch="Fill" />
</InkCanvas.Background>
<InkCanvas.RenderTransform>
<ScaleTransform x:Name="InkCanvasScaleTransform" />
</InkCanvas.RenderTransform>
</InkCanvas>
</Grid>
Code
private void ScaleUpButton_Click(object sender, RoutedEventArgs e) {
InkCanvasScaleTransform.ScaleX += .2;
InkCanvasScaleTransform.ScaleY += .2;
}
private void ScaleDownButton_Click(object sender, RoutedEventArgs e) {
InkCanvasScaleTransform.ScaleX -= .2;
InkCanvasScaleTransform.ScaleY -= .2;
}
private void InkRedAttributesButton_Click(object sender, RoutedEventArgs e) {
DrawingAttributes inkAttributes = new DrawingAttributes();
inkAttributes.Height = 12;
inkAttributes.Width = 12;
inkAttributes.Color = Colors.Red;
inkAttributes.IsHighlighter = true;
myCanvas.DefaultDrawingAttributes = inkAttributes;
}
private void InkBlueAttributesButton_Click(object sender, RoutedEventArgs e) {
DrawingAttributes inkAttributes = new DrawingAttributes();
inkAttributes.Height = 12;
inkAttributes.Width = 12;
inkAttributes.Color = Colors.Blue;
inkAttributes.IsHighlighter = true;
myCanvas.DefaultDrawingAttributes = inkAttributes;
}
Screenshots
Scaled 100%
Scaled 60%
Scaling the InkCanvas doesn't always fix the problem especially if you are wanting to save the scaled ink into a gif image file.
Apparently an InkStroke's PointTransform only transforms the location of the stroke's points, but not the size of the PenTip used to draw the stroke. (Not documented anywhere that I can find, but discovered by trial and error. The name 'PointTransform' is a bit of a clue)
So as well as applying your scaling factor to the PointTransform, you also have to scale the PenTip as follows (modification to your original code):
float thisPointScale = thisScale * stroke.PointTransform.M11;
stroke.PointTransform = Matrix3x2.CreateScale(new Vector2(thisPointScale));
stroke.DrawingAttributes.PenTipTransform = Matrix3x2.CreateScale(new Vector2(thisPointScale));
Hope this helps someone...
To resize the thickness of the strokes you have to change the Size property of the DrawingAttributes. PenTipTransform doesn't work for pencil - it throws an exception.
The point is that you cannot set the DrawingAttributes property of the stroke directly: https://learn.microsoft.com/en-us/uwp/api/windows.ui.input.inking.inkdrawingattributes
Here is example how to get this:
static IEnumerable<InkStroke> GetScaledStrokes(IEnumerable<InkStroke> source, float scale)
{
var scaleMatrix = Matrix3x2.CreateScale(scale);
var resultStrokes = source.Select(x => x.Clone()).ToArray();
foreach (var inkStroke in resultStrokes)
{
inkStroke.PointTransform = scaleMatrix;
var da = inkStroke.DrawingAttributes;
var daSize = da.Size;
daSize.Width = daSize.Width * scale;
daSize.Height = daSize.Height * scale;
da.Size = daSize;
inkStroke.DrawingAttributes = da;
}
return resultStrokes;
}
Complete example: https://github.com/ycherkes/ScaledInks
So I am just trying to draw a x and y axis on a canvas in a WPF window. I had the canvas in my main window, and my draw method worked, however I tried to put this canvas in a new window and my program crashed. (When in a new window) The program crashes when it reaches a call to gCanvas.Height or gCanvas.Width, but if I change these to gCanvas.ActualHeight and gCanvas.ActualWidth the program runs, but it does not draw the x and y axis. Does anyone know why the method works when the canvas is in my main window but not in my new window, here is the code :
private void draw()
{
Line xAxis = new Line();
Line yAxis = new Line();
xAxis.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
xAxis.X1 = 0;
xAxis.X2 = gCanvas.Width;
xAxis.Y1 = (gCanvas.Height / 2);
xAxis.Y2 = (gCanvas.Height / 2);
xAxis.HorizontalAlignment = HorizontalAlignment.Left;
xAxis.VerticalAlignment = VerticalAlignment.Center;
xAxis.StrokeThickness = 2;
gCanvas.Children.Add(xAxis);
yAxis.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
yAxis.X1 = (gCanvas.Width / 2);
yAxis.X2 = (gCanvas.Width / 2);
yAxis.Y1 = 0;
yAxis.Y2 = gCanvas.Height;
yAxis.HorizontalAlignment = HorizontalAlignment.Left;
yAxis.VerticalAlignment = VerticalAlignment.Center;
yAxis.StrokeThickness = 2;
gCanvas.Children.Add(yAxis);
}
Here is the XAML for the 2nd window
<Window x:Class="Control.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="362" Width="412">
<Grid>
<Canvas Name="gCanvas" Margin="5" Background="White"/>
</Grid>
</Window>
and i call my draw function like this
public Window1()
{
InitializeComponent();
if (this.IsLoaded)
draw();
}
So, your question is
Does anyone know why the method works when the canvas is in my main window but not in my new window?
Well, no. You are the only person that can know that. However, from your problem description, I can tell that your gCanvas control has not been rendered at the time that you have called your draw method. I can tell this because your Exception was thrown at the gCanvas.Width call, so that must have had a value of Nan, or Not a Number.
The ActualWidth property will not return that value, so that is why that worked. As the shape was not drawn, I could therefore assume that the ActualWidth property returned 0. So if this is the case, then that points to the fact that you must be calling the draw method too early and if you're doing it in the constructor, then that is too early. Instead, handle the FrameworkElement.Loaded Event on the Window:
public YourWindow()
{
...
Loaded += YourWindow_Loaded;
}
private void YourWindow_Loaded(object sender, RoutedEventArgs e)
{
draw();
}