best way for clickable image map in wpf - c#

I have an image with many parts in c# WPF
I want make each part click make think
I have tried to split the image to parts and make event on each
image but the problem is the nested part of images
what is the best way to make image map ?

You can do it easily with Expression Design which is included in Microsoft Expression Studio. This is steps you gonna do:
Add image to Expression Design.
Then you can use PaintBrush tool for split image into parts as you want.
Then you must export this to xaml. In export window you can choose Xaml Silverlight 3
Canvas as format and Paths as Text.
As you understand, it automatically converts objects you draw on your image to path object with all coordinates on it.
Then you can copy exported xaml and paste it to your application.
You can download Expression Studio from Dreamspark for free.
I have just made sample and exported it to xaml:
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Untitled1" Width="62" Height="62" Clip="F1 M 0,0L 62,0L 62,62L 0,62L 0,0" UseLayoutRounding="False">
<Canvas x:Name="Layer_1" Width="62" Height="62" Canvas.Left="0" Canvas.Top="0">
<Image x:Name="Image" Source="Untitled1_files/image0.png" Width="1920" Height="1080" Canvas.Left="0" Canvas.Top="0">
<Image.RenderTransform>
<TransformGroup>
<MatrixTransform Matrix="1,0,0,1,-929.667,-510.667"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
<Path x:Name="Path" Width="159.722" Height="161.743" Canvas.Left="82.757" Canvas.Top="-0.415951" Stretch="Fill" Fill="#FFE7DEDE" Data="F1 M 82.8307,30.8333C 81.8859,46.01 90.3304,60.6249 90.3304,75.831C 90.3304,88.8304 91.9427,101.93 90.3304,114.829C 89.0281,125.247 87.0101,136.367 90.3304,146.327C 95.3301,161.327 119.518,161.327 135.328,161.327C 157.018,161.327 175.778,144.86 193.825,132.828C 209.523,122.363 235.198,120.495 241.823,102.83C 243.994,97.0391 240.326,90.2367 237.323,84.8306C 230.656,72.8294 223.759,60.756 214.824,50.3323C 205.057,38.9377 205.748,18.0458 192.325,11.3342C 183.723,7.03329 173.332,8.29683 163.827,6.83447C 144.945,3.92956 125.479,-3.30947 106.83,0.834766C 94.3289,3.61269 83.6265,18.0524 82.8307,30.8333 Z "/>
</Canvas>
</Canvas>
The exported part is Path object. You can do whatever you want on it. For example you can handle MouseClick event for this path and change background of path....

You could overlay the image with a set of transparent shapes:
<Canvas>
<Image Source="..."/>
<Ellipse Canvas.Left="50" Canvas.Top="50" Width="100" Height="50"
Fill="Transparent" MouseDown="Ellipse_MouseDown"/>
<Path Data="..." MouseDown="Path_MouseDown"/>
</Canvas>
These shapes could be simple Rectangles or Ellipses, or more or less complex Polygons or Paths.

Its very simple. rather than go to any complicated way follow this,It's simplest
1)First declare your image in XAML
<Grid Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Name="imagePath" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
2)Find mouse position on image itself to get x and y co-ordinate
String[] arr = new String[2];
var mousePos = Mouse.GetPosition(imagePath);
arr = mousePos.ToString().Split(',');
double x = Double.Parse(arr[0].ToString());
double y = Double.Parse(arr[1].ToString());
3)Declare area where you wants to get clickable area or mousehover
if (x >= 10 && y >= 10 && x <= 20 && y <= 20//this declares square area with x1,y1,x2,y2
{
//do whatever you want to do
//don't forget to add left and top each time
left = left +x;//x=10 i.e x1
top = top + y;//y=20 i.e y1
}
4)Add this x1 and y1 each time you move on image
int left = 0;
int top = 0;
Entire code;ll look like this
InitializeComponent();
imagePath.MouseMove += new MouseEventHandler(myMouseMove);
private void myMouseMove(object sender, MouseEventArgs e)
{
String[] arr = new String[2];
var mousePos = Mouse.GetPosition(imagePath);
arr = mousePos.ToString().Split(',');
double x = Double.Parse(arr[0].ToString());
double y = Double.Parse(arr[1].ToString());
int x = (int)xx;
int y = (int)yy;
int left = 0;
int top = 0;
Console.WriteLine("Screen Cordinate-------------------------" + x + ", " + y);
for (int i = 0; i < n; i++)
{
if (x >= x1 && y >= y1 && x <= x2 && y <= y2
{
Mouse.OverrideCursor = Cursors.Hand;
left = left + x1;
top = top + y1;
break;
}
else
{
Mouse.OverrideCursor = Cursors.Arrow;
}
}
where x1,y1,x2,y2 are the cordinates on image
Thats it!!!

I think use Adorner to implement the funtions is best way.
Here is smaple for add control on image (image annotating) and Adorner overview in MSDN

Related

how to check if an element is going out of its parent in c# wpf

I am creating shapes at run time on canvas in my app and except all the shapes, ellipse is going out of canvas. How do I restrict it to canvas? All other shapes are contained in canvas because of the control points at their vertices. How do I keep a check as to not let ellipse go out of canvas without clipping. I have used ClipToBounds and it doesn't meet my needs.
Also, an alternate solution is if I can add a controlpoint at the left side of ellipse of radiusX property. I can't add a controlpoint to left side of radiusX on ellipse. If you could help me with either of that?
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusYProperty, 1, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
//radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
//radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
//shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
Here is a quick example of what i would do. It could be improved on and the code is mainly written to be easy to read and follow. It also does not handle the possibility to if the shape's size is bigger than the Canvas (not sure if that is a use case in your project).
For the example I used the "Loaded" event on the Canvas, to reset the position before drawing. You would want this check before you draw the Ellipse object.
private void TestCanvas_Loaded(object sender, RoutedEventArgs e)
{
//canvas = 450 x 800
Ellipse test_ellipse = new Ellipse();
test_ellipse.Width = 100;
test_ellipse.Height = 100;
test_ellipse.Fill = Brushes.Red;
Canvas.SetLeft(test_ellipse, 700);
Canvas.SetTop(test_ellipse, -500);
Reset_Ellipse_Bounds(TestCanvas, ref test_ellipse);
TestCanvas.Children.Add(test_ellipse);
}
private void Reset_Ellipse_Bounds(Canvas myCanvas, ref Ellipse myEllipse)
{
var left = Canvas.GetLeft(myEllipse);
var top = Canvas.GetTop(myEllipse);
//handle too far right
if (left + myEllipse.Width > myCanvas.ActualWidth)
Canvas.SetLeft(myEllipse, myCanvas.ActualWidth - myEllipse.Width);
//handle too far left
if(left < 0)
Canvas.SetLeft(myEllipse, 0);
//handle too far up
if (top < 0)
Canvas.SetTop(myEllipse, 0);
//handle too far down
if (top + myEllipse.Height > myCanvas.ActualHeight)
Canvas.SetTop(myEllipse, myCanvas.ActualHeight - myEllipse.Height);
}
For Completeness the XAML:
<Grid>
<Canvas x:Name="TestCanvas" Loaded="TestCanvas_Loaded" />
</Grid>
The idea is to check the bounding box against the Canvas edges. There are ways to improve this, but i figured the simplest solution is easier to follow.
Within each if statement you could add more logic or a method to do further processing, but this should answer the general question of knowing if it is outside the parent.
Just set ClipToBounds="true" to its father control, it avoids the canvas to be drawn outside of it.
In my case I set it to Grid as followed :
<Grid x:Name="MainGrid" Background="WhiteSmoke" ClipToBounds="true">
<Canvas Margin="10" Background="Transparent"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel">
<Canvas x:Name="canvas" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform"/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="canvas2" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform2"/>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
</Grid>

How to set boundary of a TextBlock in a grid?

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.

Canvas size as a bounding box of the contained elements

I am implementing a user control that is a view for an arrangement of shapes, which can be moved around and zoomed with the mouse, altogether or individually. The user control contains a single "Root" canvas to which the shapes are added, and the render transform of which determines its position and size in the view.
What I would like to be able is to set the background of the canvas to a rectangle that encloses all its contained shapes. Is this possible ? Of course I can set the Width and Height properties but they seem to count from the origin only. But the shapes can as well be placed at negative coordinates relative to the canvas' origin so the background rectangle of the canvas would have to start at negative coordinates also.
Canvas cnv = new Canvas();
// show the canvas' dimensions
cnv.Background = Brushes.AliceBlue;
for (int i = -5; i < 5; i++)
{
for (int j = -5; j < 5; j++)
{
Rectangle newRectangle = new Rectangle();
cnv.Children.Add(newRectangle);
newRectangle.Width = 7;
newRectangle.Height = 7;
newRectangle.Fill = Brushes.Green;
Canvas.SetTop(newRectangle, j * 10);
Canvas.SetLeft(newRectangle, i * 10);
}
}
// these are the right bounding dimensions, but with the wrong origin
cnv.Width = 107;
cnv.Height = 107;
// nothing available like that?
// cnv.Left = -50;
// cnv.Top= -50;
Of course, if everything fails, I could just add another rectangle for the bounds.
The Canvas is transparent so why not stack it on top of a rectangle in a grid:
<Grid>
<Rectangle Fill="Lime" Stroke="Red" StrokeThickness="5" />
<Canvas />
</Grid>
Or if it realy has to look like a border:
<Border Background="Lime" BorderBrush="Red" BorderThickness="5">
<Canvas />
</Border>

Place existing XAML vector graphics in canvas

I want to make a product configurator. I want to display the product in the main window with xaml vector graphics and then export a dxf file (as technical drawing.) The dxf export already works.
The main part of the product is drawed with lines, no problem here (drawed as a rectangle). But other installations are more complex, for example a 230V-outlet. The outlet is static, so I don't need to draw it with code. I created a outlet.xaml file with Inkscape. How can I place that xaml-file on a specific place (coordinates) in my canvas?
In dxf it was very easy - I created a block as external dxf file, and i could insert it in the drawing like this:
DxfDocument doc = new DxfDocument();
doc.DrawingVariables.InsUnits = DrawingUnits.Millimeters;
//Insert existing DXF
netDxf.Blocks.Block Steckdose = netDxf.Blocks.Block.Load("Steckdose.dxf");
Insert i = new Insert(Steckdose, new Vector2(200,200));
doc.AddEntity(i);
doc.Save("test.dxf");
Is there any way to do that with xaml? Please notice: the number and position of the outlets should be variable, so I wanted to draw them with c# code.
For testing, I made a window with two textboxes: one for the with of the rectangle, one for the x coordinate of the outlet.
Thats my code so far:
private void button_Click(object sender, RoutedEventArgs e)
{
int value1;
int value2;
if (int.TryParse(txt_laenge.Text, out value1) & int.TryParse(txt_laenge.Text, out value2))
{
Länge = value1;
int SD = value2;
var rechteck = new System.Windows.Shapes.Rectangle();
rechteck.Stroke = new SolidColorBrush(Colors.Black);
rechteck.Height = 136;
rechteck.Width = Länge;
Canvas.SetLeft(rechteck,0);
Canvas.SetTop(rechteck, 0);
IV.Children.Clear();
IV.Children.Add(rechteck);
//Place Steckdose.xaml at coordinates (SD, 68);
}
else
{
MessageBox.Show("Ungültige Eingabe!");
}
}
Edit: The Steckdose(outlet).xaml looks like that:
<?xml version="1.0" encoding="UTF-8"?>
<!--This file is NOT compatible with Silverlight-->
<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Stretch="Uniform">
<Canvas Name="svg8" Width="44" Height="44">
<Canvas.RenderTransform>
<TranslateTransform X="0" Y="0"/>
</Canvas.RenderTransform>
<Canvas.Resources/>
<!--Unknown tag: sodipodi:namedview-->
<!--Unknown tag: metadata-->
<Canvas Name="layer1">
<Canvas.RenderTransform>
<TranslateTransform X="0" Y="-253"/>
</Canvas.RenderTransform>
<Rectangle xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Canvas.Left="0.13150091" Canvas.Top="253.1315" Width="43.737" Height="43.737" RadiusX="4.4731021" RadiusY="4.4731021" Name="rect3680" Fill="#FF008000" StrokeThickness="0.26300183" Stroke="#FF000000"/>
<Ellipse xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Canvas.Left="2.7" Width="38.6" Canvas.Top="255.7" Height="38.6" Name="path4487" Fill="#FF008000" StrokeThickness="0.35593221" Stroke="#FF000000"/>
<Ellipse xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Canvas.Left="9.7" Width="4.6" Canvas.Top="272.7" Height="4.6" Name="path4491" Fill="#FF000000" StrokeThickness="0.36692113" Stroke="#FF000000"/>
<Ellipse xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Canvas.Left="29.7" Width="4.6" Canvas.Top="272.7" Height="4.6" Name="path44915" Fill="#FF000000" StrokeThickness="0.36692113" Stroke="#FF000000"/>
</Canvas>
</Canvas>
</Viewbox>
This should work:
UIElement element;
using (var stream = new FileStream("Outlet.xaml", FileMode.Open, FileAccess.Read))
{
element = (UIElement)XamlReader.Load(stream);
}
Canvas.SetLeft(element, 100);
Canvas.SetLeft(element, 50);
canvas.Children.Add(element);

Rotating a border doesn't change window size

I try to rotate a Border and have the MainWindow change his size based on the new space taken by the Border rotation.
I've set SizeToContent="WidthAndHeight" but window size does't take change when I rotated the border.
Do I need to programmatically set the Width and Height for the MainWindow or this can be achieved changing the xaml code in some other way?
My xaml code:
<Window x:Class="MyClass.MainWindow"
WindowStyle="None" AllowsTransparency='True'
Topmost='False' Background="Transparent" ShowInTaskbar='False'
SizeToContent="WidthAndHeight" WindowStartupLocation="Manual">
<Border Name="MyBorder"
BorderBrush="Transparent"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5">
</Border>
</Windows>
My c# code on Window_KeyDown:
# RotateTransform rt = new RotateTransform() is declared at class level.
if (e.Key == Key.I)
{
if (rt.Angle + 1 < 360)
{
rt.Angle += 1;
}
else
{
rt.Angle = 0;
}
MyBorder.RenderTransform = rt;
}
Use LayoutTransform instead of RenderTransform
From MSDN: Transforms Overview
LayoutTransform – A transform that is applied before the layout pass. After the transform is applied, the layout system processes the
transformed size and position of the element.
RenderTransform – A transform that modifies the appearance of the element but is applied after the layout pass is complete. By using the
RenderTransform property instead of the LayoutTransform property, you
can obtain performance benefits.
Example
<Border Name="MyBorder"
BorderBrush="Transparent"
Background="Transparent"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5">
<Border.LayoutTransform>
<RotateTransform Angle="90"/>
</Border.LayoutTransform>
</Border>
So in your case
RotateTransform rt = new RotateTransform(0.0, 0.5, 0.5);
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.I)
{
if (rt.Angle + 1 < 360)
{
rt.Angle += 1;
}
else
{
rt.Angle = 0;
}
MyBorder.LayoutTransform = rt;
}
}}

Categories