Deleting Only the CoordinateSystem from the Canvas - c#

I'm trying to draw a tree on a Canvas with a coordinate system. I'm not yet that far that I could draw the tree itself, at the moment I'm trying to get the coordinate system a little bit more flexible, so it gets redrawn when I resize the window and the canvas within it.(That means expanding/reducing the axes of it, not the scale.)
I have already read some topics about canvas and the deleting of its children elements and I found a solution. The problem is that this solution does not work out that fine for me, because it keeps deleting only one line of the coordinate system on every resize event.
All lines that are used to draw the coordinate system got the UID starting with CoordinateSystemIn.
Currently my code for this is:
Canvas = Anzeige;
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (MainWindowLoaded)
{
foreach (UIElement Element in Anzeige.Children)
{
if (Element.Uid.StartsWith("CoordinateSystemIn"))
{
Anzeige.Children.Remove(Element);
}
}
Stift.DrawCoordinateSystem(Anzeige.ActualWidth, Anzeige.ActualHeight, 25);
}
}
Is there any other solution which redraws the whole coordinate system at once and not only line for line at each resize event?

Put you Canvas in a ViewBox - the latter will automatically resize the canvas to the appropriate size. Your code could look like the following:
<Window x:Class="WpfAccessControlFromResources.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Viewbox>
<Canvas Width="80"
Height="80">
<Path Stroke="DarkGray">
<Path.Data>
<GeometryGroup>
<EllipseGeometry RadiusX="40" RadiusY="40" Center="40, 40" />
<LineGeometry StartPoint="15, 40" EndPoint="65, 40" />
<LineGeometry StartPoint="40, 15" EndPoint="40, 65" />
</GeometryGroup>
</Path.Data>
</Path>
</Canvas>
</Viewbox>
</Window>
This way you do not have to mess with the coordinates yourself.
Update after Marv's comment:
Actually, I think you should encapsulate your coordinate system in a separate class. This class would derive from System.Windows.Controls.Panel and would override the ArrangeOverride and MeasureOverride methods to position the child elements. Within these former method, you could also add / remove unnecessary PathGeometries that show the coordinate systems. The code could somewhat look like this:
public class CoordinateSystem : Panel
{
// This element will display the actual coordinate system
// It must be added to the collection of InternalChildren
private Path _coordinateSystemPath;
// This method is used to determine how much space the children want to have
protected override Size MeasureOverride(Size availableSize)
{
Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
Size desiredSize = new Size();
// Measure how many space the items you want to display need
foreach (var child in InternalChildren)
{
child.Measure(infiniteSize);
// Check at which position this child wants to be and
// calculate the desired size of the coordinate system
}
return desiredSize;
}
// this method will arrange the path for the coordinate system as well as
// all the children that will be displayed
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var child in InternalChildren)
{
if (child == _coordinateSystemPath)
{
// Update the PathGeometry of this path according to provided finalSize here
continue;
}
child.Arrange(child.DesiredSize);
}
}
}
Please note that this is only a sketch and not the whole class. In this case you could also integrate zoom factor and translation of the point of origin of the coordinate system if you'd want to.
Anyway, while this solution is maybe the cleanest regarding Separation of Concerns, it also requires a good understanding about the WPF layout system and quite some work and testing. You can read more about this topic here: http://msdn.microsoft.com/en-us/library/ms754152(v=vs.110).aspx

Perhaps what you might like to use is the Viewbox class. This will do all of your resizing for you automatically (without a single line of procedural code). Take this example:
<Viewbox>
<Path StrokeThickness="1" Stroke="Black" Data="M0,0 L25,25 M0,25 L25,0"/>
</Viewbox>
Here we have a simple Path 'X' to represent your Canvas content. It's quite small initially, or without the Viewbox, just 25 X 25 pixels. However, once inside a Viewbox, it can automatically fill its provided space. You might also find that the Stretch and StretchDirection properties are of use to you.
Just add your Canvas into a ViewBox and experiment with it to see if it will meet your needs.

Related

Trying to animate such that the other rectangle below it moves upward?

<StackPanel x:Name="rootStackPanel" Background="{ThemeResource SystemControlAcrylicWindowBrush}" Padding="0,48">
<Rectangle x:Name="sampleRectangle" Width="200" Height="300" Fill="DeepPink" DoubleTapped="Rectangle_DoubleTapped" RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<CompositeTransform TranslateY="-100"/>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="otherRectangle" Width="200" Height="200" Fill="Cyan"/>
</StackPanel>
I want to achieve a dynamic look, where, when the pink rectangle is moved upward, the blue rectangle takes up the left over space. Such that it is always touching the pink rectangle.
I have tried manipulating offsets and scale properties provided by visual layer but the actual height is not affected by any of the property, as a result the other rectangle just stays in the original position. Now I am trying to achieve that effect using storyboards animation. But as you can see, the translation property (and the scale property as well) doesn't effect the actual container of the control somehow but rather makes the content in it move to whatever translation.
So, what properties do I need to manipulate to achieve the effect where the other rectangle inside the stackpanel moves dynamically with the changes to the first rectangle?
I know adjusting width or height property would result in what I am trying to achieve but for more complex scenarios where there might be a textbox rather than simple rectangle that is being animated, there is sort of a weird animation of the placeholder text(shrinking of text), which is something I don't want!
So, what properties do I need to manipulate to achieve the effect where the other rectangle inside the stackpanel moves dynamically with the changes to the first rectangle?
You could use Microsoft.Toolkit.Uwp.UI.Animations.Behaviors to realize this feature directly. Before animate your rectangle, you need to get the absolute position of the each rectangle like the follow.
var scgt = sampleRectangle.TransformToVisual(Window.Current.Content);
Point screenCoords = scgt.TransformPoint(new Point(0, 0));
var ddv = otherRectangle.TransformToVisual(Window.Current.Content);
Point Res = ddv.TransformPoint(new Point(0, 0));
If you want pink rectangle to move upward, you could Offset offsetY value
sampleRectangle.Offset(offsetX: 0, offsetY: -(float)screenCoords.Y - (float)sampleRectangle.Height, duration: 2500).Start();
And then animate otherRectangle like the follow
otherRectangle.Offset(offsetX: -(float)Res.X, offsetY: -(float)Res.Y).Scale(scaleX: 2, scaleY: 2).Start();
You need keep scaleX equal with scaleY when you scale TextBox.Otherwise, the TextBox will be deformed.

Rotating cursor without using WinForms

Is it possible to rotate FrameworkElement.Cursor?
My application allows user to rotate objects around their center. Once rotated, default resize cursors appear awkward on top of tilted borders.
My first thought was to apply RotateTransform to the Cursor property, but it looks like we can't do that in XAML. Next I tried inheriting from Cursor class, but it looks like MS guys have sealed it.
Another way is to set default cursor to None and use my own image (with transform) and set its position upon MouseMove. I'm not willing to go down that path if there are easier alternatives. Anyone with a good suggestion?
I'm looking for a WPF-only solution if at all possible.
Finally managed it within the bounds of WPF, without using WinForms or PInvokes. Instead of creating custom cursors (*.cur) on the fly or converting Visuals into cursors, I used MouseMove event of the parent control along with a WPF element (Path) as my cursor. Here's the way just in case anyone is interesed:
Set the Cursor of your resize thumb (or whatever you're using as the border of your shape) to None, so that WPF doesn't display default arrow.
Create your own cursor. Could be any FrameworkElement, but I have used Path for its easy manipulation to create any shape you want. Note that most of the properties I have set below are important.
<Path x:Name="PART_EW"
Data="M0,20 L25,0 25,15 75,15 75,0 100,20 75,40 75,25 25,25 25,40z"
Fill="White" Stroke="Black" StrokeThickness="1" Visibility="Collapsed"
Width="50" Height="20" Opacity=".7" Stretch="Fill" Panel.ZIndex="100001"
HorizontalAlignment="Left" VerticalAlignment="Top" IsHitTestVisible="False"
/>
Add the following code in your resize thumb:
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
var Pos = e.GetPosition(this);
PART_EW.Margin = new Thickness(
Pos.X - PART_EW.Width / 2,
Pos.Y - PART_EW.Height / 2,
-PART_EW.Width,
-PART_EW.Height);
PART_EW.Visibility = Visibility.Visible;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
PART_EW.Visibility = Visibility.Collapsed;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
var Pos = e.GetPosition(designerItem);
PART_EW.Margin = new Thickness(
Pos.X - PART_EW.Width / 2,
Pos.Y - PART_EW.Height / 2,
-PART_EW.Width,
-PART_EW.Height);
}
Note that I've not set RotateTransform of my Path anywhere in the code, since it is already part of the resize thumb and therefore acquires the angle of the parent control automatically.
Hope this helps people down the road.

WPF Custom shaped button

I have a circle shaped button and I like to add a pair of Angel Wings to it, but I can't seem to draw it correctly.
I'd like to draw a border with an angel wing on the left and a circle in the middle and an angel wing on the right, in one custom shape..
the Custom shape is the border of the button.
The button is a resizable button, it means it can change Height & Width with the Window, and still maintain the same position.
this button is just an example of what I have made so far.
I've searched for a solution on Google and this site but can't find anything that can help me..
You can use software like Inkscape and with that you can open most image format and from images you can use the trace bitmap option to trace a path. This will create a <path> object in a <canvas>. It can then be used as a background.
things to note :
The quality highly depends on the image source quality and precision. So if your image have lots of color and gradient, huge line thickness and uses anti aliasing you will get very bad result because of a huge amount of vector on the path result. if you use thinner lines and very little color with no gradient you can get amazing conversion result.
You can also hand trace over the image and use that instead of the automatic option. You will get better result like that but it require more work.
With this path created you can create a resource in your application dictionary and use it for icon or background on anything you want. It is scalable in any direction without quality lost as it is now a vector "image".
try looking for image/Photoshop/any other image or drawing method to Xaml converter
you can create an image and convert it to xaml
see http://vectormagic.com
taken form here
Can you try this if it helps
Create Custom control , something like this
<Grid ClipToBounds="True" HorizontalAlignment="Center" VerticalAlignment="Center">
<ed:Arc x:Name="KnobA" ArcThickness="1" ArcThicknessUnit="Percent" EndAngle="360" Height="120" Stretch="None"
Stroke="Black"
StartAngle="0" Width="120" RenderTransformOrigin="0.5,0.5" StrokeThickness="0">
<ed:Arc.Fill>
<ImageBrush ImageSource="/Gazelle;component/Resources/Images/image.png" Stretch="UniformToFill" />
</ed:Arc.Fill>
</ed:Arc>
</Grid>
Use it in your other XAML
<controls:BoundRotaryButton Grid.Column="1" Margin="0,0,35,30"
VerticalAlignment="Bottom" HorizontalAlignment="Right" Opacity="0.2"
Grid.ColumnSpan="2"
BoundRotaryButtonState="{Binding myvm.RotaryPressed, Mode=OneWayToSource,
NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
Create the dependency in code behind of the button control and the handler for mouse clicks.
public static readonly DependencyProperty KnobAPushButtonStateProperty = DependencyProperty.Register("KnobAPushButtonState", typeof (bool), typeof (KnobA));
public bool KnobAPushButtonState
{
get { return (bool) GetValue(KnobAPushButtonStateProperty); }
set { SetValue(KnobAPushButtonStateProperty, value); }
}
private void KnobA_MouseDown(object sender, MouseEventArgs e)
{
Mouse.Capture(this);
KnobAPushButtonState = true;
private void KnobA_MouseUp(object sender, MouseEventArgs e)
{
Mouse.Capture(null);
KnobAPushButtonState = false;
}
In your viewmodel you will know when this dependency property changes the button is pressed or released and invoke the command you need.
I have something like a circular button, which you can rotate and press and so on. But you can use any shape you like with Blend.

Drawing a fading grid in WPF

I'm trying to draw a graph-paper like grid as the background of a Canvas. This grid is different from most explanations of how to do this that I've found because the canvas can be scaled to implement zooming. What I want to do is have a series of scales of grid lines, i.e. at every 10^n units. Then, the grid lines should fade out as they become close together due to zooming. In other words if n is large, the lines associated with that grid should be darker/heavier weight than those for a smaller n.
This was easy to do in WinForms, I implemented it by overriding OnPaint and defining the color of the line to be a function of the distance to the next grid line. Lines far apart were given a heavier weight than lines close together.
I have not figured out how to do this in WPF. I can sort of get this behavior by creating a line that has a StrokeThickness according to the spacing of the grid lines, but this only works for a small range of StrokeThickness and scaling values. It would work if it were possible to define a line as having a very heavy weight, but still a small StrokeThickness.
Even doing this via implementing a custom control with OnRender is difficult because I have not found a reliable way to get the scale of the control while rendering it (the ScaleTransform is part of one of the parent controls, not the immediate parent).
Any thoughts on how to accomplish this goal would be much appreciated!
I solved this by NOT adding the grid to the canvas but by stacking the canvas on top of another control that contains the grid:
<Grid>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="DrawingLayer" />
</Grid>
When zooming events occur I simply redraw the GridLayer.
This allowed me to only draw the lines that are needed, to draw them exactly how I want them and, in my case very important because I had potentially a gazillion grid lines, I did not need to draw the lines any longer/taller than needed. This way I conserved a lot of CPU time.
Another thing to note is that I implemented my own zoom code. I did not use a RenderTransform or a ViewBox because I wanted the line to stay at the same width. All I did was keep track of the coordinates of the top left corner to support panning and the zoomlevel. As soon as one of these changes I redraw the canvases. I wrote two functions: one transforms a coordinate on the Canvas to a graph coordinate and the other one does the reverse. The first method allows me to translate cursor coordinates to graph coordinates and the second one will turn the coordinates of the graph into points that can be used to draw on the canvas.
Untested code and making a lot of assumptions about the orientation of axis:
Point Graph2Canvas(Point graphPoint)
{
var canvasPoint = new Point(graphPoint);
canvasPoint.X *= zoomLevel;
canvasPoint.Y *= zoomLevel;
canvasPoint.X -= topLeft.X;
canvasPoint.Y -= topLeft.Y;
return canvasPoint;
}
This can be optimized and the truth is I created more functions that do the same thing for collections of points.
Extra:
I ended up with a far more complex setup that looked a bit like this:
<Grid>
<Canvas x:Name="BackgroundLayer"/>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="AxisLayer"/>
<Canvas x:Name="DrawingLayer" />
<Canvas x:Name="SelectionBoxLayer"/>
<Canvas x:Name="CursorLayer"/>
</Grid>

How do you get the REAL position of objects in silverlight?

How do you get the REAL position of objects in silverlight?
I have a header image centered on the screen. When I make the browser window smaller, obviously, the header's left side goes off the screen. Finding out the actual position is good to know if you want to position objects on top of the image.
I capture the Content_Resized and I run a little test:
if (App.Current.Host.Content.ActualWidth > header.Width)
{
TEST = Canvas.GetLeft(header);
}
else
{
TEST = Canvas.GetLeft(header);
}
TEST always returns zero.
EDIT: header sits on a grid instead of a canvas. "Well, there is your problem..." So a better question might be this. How would I get the margins of an image sitting on a grid?
I probably should just answer the question but how to find the position of an element relative to another is probably something that has been answered before (by myself and others) here and elsewhere on the tinternet.
However if your goal is to place an item over an image then place the image in a Grid and then add the item as child of the Grid. That way you assign the relative position over the image as the margin of the item and let Silverlight's layout system do the rest.
As a general rule if you feel that you need to write code to move stuff about when the size of things change then unless you are writing a custom panel or something you're probably not using Silverlight layout system properly.
Edit:
Try this experiment:-
<Grid x:Name="LayoutRoot">
<Grid x:Name="headerContainer" Margin="50, 60, 0, 0" HorizontalAlignment="Left" VerticalAlignment="Top">
<Image Source="YourLargeImage" />
<Image Source="YourSmallerImage" HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>
</Grid>
Now try changing the inner grid's Margin to move its position around the screen. Note the smaller image always remains at the top center of the large image.
I got it working.
First of all, these images are on a grid, not a canvas. But switching the grid to a canvas caused lots of other problems one of which is that I could not have the header image centered like before.
The solution was to change the margin of the smaller image sitting on top of the larger header image when the content resized like this:
blankbarimage.Margin = new Thickness((App.Current.Host.Content.ActualWidth - header.Width) / 2, 0, 0, 0);
and, by the way, you create a content resized method like this:
App.Current.Host.Content.Resized += new EventHandler(Content_Resized);
So, to answer my own question, the way you get the REAL position of object in silverlight is (if they are on a grid) by looking at their margin settings.

Categories