I am currently designing a layout app in Silverlight and have a Canvas inside of a Viewbox. I add shapes to the canvas and they display properly, when I resize the viewbox to zoom in at 2x the height and width, everything still draws properly.
The problem comes when I try to zoom at a factor of 4 or greater or at 0.5 (zoomed out).
Update: The horizontal lines are still there, they are just not drawing. Interaction between the the other shapes and the disappearing ones is still present
When I do this, any horizontal lines do not redraw, but any other shapes, vertical lines of other, still redraw fine. The objects are still children of the canvas and their visibilities are all set to visible.
What is happening?
Update
Very Simple XAML:
<ScrollViewer x:Name="scrollViewer"
Padding="0"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
IsTabStop="False"
Background="Beige">
<Viewbox x:Name="viewBox" Stretch="UniformToFill">
<Canvas x:Name="designCanvas"
Background="{Binding ElementName=mainControl, Path=Background, Mode=TwoWay}">
</Canvas>
</Viewbox>
</ScrollViewer>
Here is how I add the shapes:
Rectangle horGuide = new Rectangle()
{
Tag = "horGuide",
Fill = new SolidColorBrush(Colors.Cyan),
Height = 0.5,
Width = designCanvas.canvActualWidth*16,
};
int h = designCanvas.horOffset;
int v = designCanvas.vertOffset;
double d = e.GetPosition(sideRule).Y;
designCanvas.Children.Add(horGuide);
Canvas.SetTop(horGuide, ((d+v )/ designCanvas.zoomFactor));
Canvas.SetLeft(horGuide, 0 - h);
To Zoom in:
viewBox.Width *= 2;
viewBox.Height *= 2;
Why not use the ScaleTransform class to zoom in and out.
Related
Why does the BorderThickness changes the rendered CornerRadius?
Also, what is the design reasoning/philosophy behind it?
I just cannot understand it, maybe I'm missing something.
<Border Width="300"
Height="300"
Background="Red"
BorderBrush="Blue"
CornerRadius="5"
BorderThickness="50" />
<Border Width="300"
Height="300"
Background="Red"
BorderBrush="Blue"
CornerRadius="5"
BorderThickness="10" />
I see Rectangle has the same behavior.
Is there any element in WPF or WinUI which I can use to draw an exact radius so I can respect the designer's requirement?
Besides Path with custom points, I do not see any other way.
The issue with Path is I need to recompute the points myself when width/height changes which will hurt performance.
EDIT: Trying to tweak the corner radius so that it can match the design spec turns out to be impossible.
For example, let's assume the designer wants a Border with CornerRadius=5 and BorderThickness = 30.
In the image below, the top Border shows what an actual CornerRadius=5 looks like.
In the bottom Border, I try to meet the design spec. I set the BorderThicikness=30 and I tweak the CornerRadius to something very small so that it looks close to the corner radius of the Border above.
But the CornerRadius remains quite big even for a very small value 0.0000002:
<Border Width="100"
Height="100"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="Red"
BorderThickness="0"
CornerRadius="5"/>
<Border Width="100"
Height="100"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="Red"
BorderBrush="Blue"
BorderThickness="30"
CornerRadius="0.0000002" />
EDIT #2:
So that it's even more obvious how big the corner radius of the bottom Border is compared to the top one:
Why does the BorderThickness changes the rendered CornerRadius?
Looking at Border's method OnRender, there is a Pen which aim at drawing a rounded rectangle if CornerRadius property is set... the pen has Thickness Property, Pen's thickness = Border's thickness.
This is the flow of execution inside OnRender method when both CornerRadius and BorderThickness are set (in case of uniform thickness and uniform radius)..
pen1 = new Pen();
pen1.Brush = borderBrush;
pen1.Thickness = borderThickness.Left;
double topLeft1 = cornerRadius.TopLeft;
double num = pen1.Thickness * 0.5;
Rect rectangle = new Rect(new Point(num, num), new Point(this.RenderSize.Width - num, this.RenderSize.Height - num));
dc.DrawRoundedRectangle((Brush) null, pen1, rectangle, topLeft1, topLeft1);
So there is a Thickness-Radius trade-off that can not be avoided, And probably the only way to deal with it is to set a base border (the border that meets the designer's guidelines) and for the other border you would increase its radius if its thickness is less than the base border's thickness!
Update
Note that the dimensions of the border (Width and Height) is the third factor in the equation (see DrawRoundedRectangle call).
Imagine you are about to draw a rectangle of size 100x100 using a pen with thickness = 30.. You start drawing from the middle of the top line and moving the pen towards the left (image below).. Now, you will start shifting the pen early before reaching the corner because you are not allowed to get out of the 100x100, and that will result in a big curve so the value of the radius will not take affect in this case because the Width and Height has higher priority that the CornerRadius.
So to let a very small radius like 0.0000002 take affect, you have to enlarge the dimensions of the rectangle to a size that a pen with thickness = 30 will look small enough to reache near the corners.
Here is the first xaml code after tweaking radius of the second border (set it to 20) to make it looks like the first border..
And to make the first border looks like the second border, you can change its code to this
<Grid>
<Border
Width="300"
Height="300"
BorderBrush="Blue"
BorderThickness="10"
CornerRadius="5" />
<Border
Width="300"
Height="300"
Background="Red"
BorderBrush="Blue"
BorderThickness="50"
CornerRadius="5" />
</Grid>
NEW INFORMATION!!!
Meanwhile i have found a solution but there is a new problem.
The solution is to set the margin of the canvas in code-behind to a new object of type Thickness with top and left 1 or 2.
But the canvas is lying on a tabcontrol.
When i switch between tabs or make a mousedown on the canvas the margin is lost.
I'm working with VS2015 on a WPF-application and have a very curious problem.
I got in one of my WPF windows a canvas as parent for some child elements.
One of these elements is a rectangle which shall show the user the size of a DIN-A4 page.
It is added in code-behind to the children collection of the canvas.
Normally i would place that rectangle at position (0,0).
But because of some problems i have to trick and set the position to (-1,-1) like that:
public static System.Windows.Shapes.Rectangle GetRectangle(double top, double left, double width, double height)
{
var rectangle = new System.Windows.Shapes.Rectangle();
System.Windows.Controls.Canvas.SetLeft(rectangle, -1);
System.Windows.Controls.Canvas.SetTop(rectangle, -1);
rectangle.Width = Math.Round(GetSizeInPoint(width)) + 2;
rectangle.Height = Math.Round(GetSizeInPoint(height));
rectangle.StrokeThickness = 1;
rectangle.Stroke = System.Windows.Media.Brushes.Red;
return rectangle;
}
But the result of it is that just a small part of the top and left border of the rectangle can be seen.
Do i have a chance to "move" the canvas so that the rectangle is displayed completely?
Hereby another important problematic point is that the Grid named "grdProtocolDesigner" can be serialized to XML and saved in the database.
So a complete restructuring would be a big problem.
Here the relevant part of my XAML including the canvas:
<ContentPresenter x:Name="protocolContainer"
Grid.Row="1"
Grid.Column="0">
<ContentPresenter.Content>
<Grid x:Name="grdProtocolDesigner"
Grid.Row="1"
Grid.Column="0">
<ScrollViewer>
<!--Important! The background of this panel has to be "Transparent" so that drag'n'drop-events can work.
Otherwise the events are not fired.-->
<Canvas x:Name="protocolDesignerPanel"
AllowDrop="True"
Visibility="{Binding DesignerPanelsVisibility}"
Width="2200" Height="4000"
MouseEnter="designerpanel_MouseEnter"
MouseLeave="designerpanel_MouseLeave"
MouseDown="designerpanel_MouseDown"
MouseMove="designerpanel_MouseMove"
MouseUp="designerPanel_MouseUp">
<Canvas.RenderTransform>
<ScaleTransform x:Name="scaleProtocolLayout"/>
</Canvas.RenderTransform>
<Canvas.Background>
<ImageBrush ImageSource="../../pictures/CanvasBackground.png"
TileMode="Tile"
Stretch="None"
Viewport="0, 0, 10, 10"
ViewportUnits="Absolute" />
</Canvas.Background>
</Canvas>
</ScrollViewer>
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
I have a rectangle on a canvas that the user can resize, move and so on to make a selection.
I also have an image the size of the screen behind the canvas (basically a screenshot).
I'd like to translate the selection (the rectangle) in the canvas to a 1:1 selection in the image (I want the image directly behind the rectangle) given I have the rectangle's Canvas.Top, Canvas.Left, Width, Height.
<Grid Name="MainGrid" SnapsToDevicePixels="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image x:Name="MainImage" Stretch="None" RenderOptions.BitmapScalingMode="HighQuality"/>
<Border Background="Black" Opacity="0.4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<Canvas Name="MainCanvas" Width="{Binding Source={x:Static SystemParameters.PrimaryScreenWidth}}" Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight}}" Background="Transparent">
<ContentControl Name="SelectionRect" />
</ContentControl>
</Canvas>
</Grid>
I tried doing this: (MainImage is the image under the canvas)
Rect rect = new Rect(Canvas.GetLeft(SelectionRect), Canvas.GetTop(SelectionRect), SelectionRect.Width, SelectionRect.Height);
Rect from_rect = SelectionRect.TransformToVisual(this).TransformBounds(rect);
BitmapSource cropped_bitmap = new CroppedBitmap(MainImage.Source as BitmapSource,
new Int32Rect((int)from_rect.X, (int)from_rect.Y, (int)from_rect.Width, (int)from_rect.Height));
SelectionRectImageSource = cropped_bitmap;
But the image I get (SelectionRectImageSource) is a moved aside version of the actual pixels behind the selection rectangle.
So basically, I don't understand how these transformations work and how I should use them if at all.
Example:
Thanks a lot!
Dolev.
Looks like you need to correct for the DPI difference between the image (usually 72dpi) and the presentation source (usually 96dpi). Additionally, your first Rect should not be offset by Canvas.Left and Canvas.Top; TransformToVisual will take care of the relative offset for you.
var source = (BitmapSource)MainImage.Source;
var selectionRect = new Rect(SelectionRect.RenderSize);
var sourceRect = SelectionRect.TransformToVisual(MainImage)
.TransformBounds(selectionRect);
var xMultiplier = source.PixelWidth / MainImage.ActualWidth;
var yMultiplier = source.PixelHeight / MainImage.ActualHeight;
sourceRect.Scale(xMultiplier, yMultiplier);
var croppedBitmap = new CroppedBitmap(
source,
new Int32Rect(
(int)sourceRect.X,
(int)sourceRect.Y,
(int)sourceRect.Width,
(int)sourceRect.Height));
SelectionRectImageSource= croppedBitmap;
Depending on where this code resides, you may also need to transform the selection rectangle to MainImage instead of this (as I did).
Also, in case MainImage.Source is smaller than the actual MainImage control, you should probably set the horizontal and vertical alignments of MainImage to Left and Top, respectively, less your translated rectangle end up outside the bounds of the source image. You'll need to clamp the selection rectangle to the dimensions of MainImage too.
This my first post on stack overflow, I hope I get it right. I am using the ZoomControl from WPF Extensions to display an image with pan and zoom support:
<DockPanel Grid.Row="1" x:Name="canvas">
<Controls:ZoomControl x:Name="zoomControl">
<Canvas x:Name="canvas">
<Image x:Name="imageControl" Stretch="None" />
</Canvas>
</Controls:ZoomControl>
</DockPanel>
When the user selects an image with a bowse dialog, I load that image like so:
bmp = new BitmapImage(new Uri(fileName));
this.imageControul.Source = bmp;
I would like to added rectangles\adorners to specific locations (pixel coordinates) on the image the user loaded based on some image processing.
var r = new Rectangle();
r.StrokeThickness = 5;
r.Stroke = Brushes.Black;
r.Fill = Brushes.Transparent;
r.Width = width;
r.Height = height;
Canvas.SetLeft(r, y);
Canvas.SetTop(r, x);
canvas.Children.Add(r);
However, the rectangles are not placed in the expected locations? Wrong scale and location.
Thanks,
John
I expect the problem is that your Canvas is expanding to fill the space rather than being locked to the rectangle. Have a look with a tool like Snoop and see what the bounding boxes of the two are.
You might be able to fix it with Horizontal and VercticalAlignment on the canvas, set them to anything other than Stretch.
If that doesn't work restructure it like this
<ZoomBox>
<Grid>
<Image/>
<Canvas/>
</Grid>
</ZoomBox>
So the Image and the canvas are grouped by the parent Grid which is being transformed.
I would like to zoom an image in WPF and that the image visual render be inside a constrained sized item control.
For example:
<Canvas x:Name="m_canvas" MaxWidth="300" MaxHeight="300" >
<Image Source="..."
Width="300"
Height="300" />
</Canvas>
The zoom code:
var matrix = ((MatrixTransform)m_image.RenderTransform).Matrix;
var center = new Point(m_image.ActualWidth / 2, m_image.ActualHeight / 2);
center = matrix.Transform(center);
matrix.ScaleAt(delta.Scale.X, delta.Scale.Y, center.X, center.Y);
((MatrixTransform)m_image.RenderTransform).Matrix = matrix;
The problem is that when I'm zooming the image render size go larger that the canvas limit (300x300). I would like if the image can zoom only in the canvas.
I don't want to limit the max zoom, I want that if the render size of the image is larger that the canvas, it's stay inside. I don't want that it overlap the canvas
You could clip to the bounds of the Canvas:
<Canvas ClipToBounds="True" ...>
But I don't understand why you're using a Canvas in the first place. It's likely that there's a much nicer way to approach your particular problem without the need for hard-coded widths and heights and without any Canvas at all.