I'm actually trying to add in a asynchronous way some UIElement to my Canvas define in my MainPage.
As I understand the basic way to add UIElement to a Canvas is to add it on his UIElementCollection, for example with a Line I should do like that:
Line line = new Line();
// Line attributes
line.Stroke = new SolidColorBrush(Colors.Purple);
line.StrokeThickness = 15;
Point point1 = new Point();
point1.X = 0;
point1.Y = 0;
Point point2 = new Point();
point2.X = 480;
point2.Y = 720;
line.X1 = point1.X;
line.Y1 = point1.Y;
line.X2 = point2.X;
line.Y2 = point2.Y;
// Line attributes
MyCanvas.Children.Add(line);
Let's imagine that I have a Class call Graphics that needs to access this Canvas in order to draw on it.
public class Graphics
{
public void drawLine()
{
//Using Dispatcher in order to access the main UI thread
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
Line line = new Line();
// Line attributes
/**
* Here I want to access the Canvas of the MainPage
* I have no Idea what to put here !!!
*
**/
});
}
}
In the place "I have no Idea what to put here !!!" I tried to access directly to the MainPage Canvas --> FAIL
I tried to declare a public static UIElementCollection in the MainPage in order ta add my UIElement then pass it to the Canvas but not possible because UIElementCollection has no constructor --> FAIL
But those ideas seems to be dirty coding and not really elegant. So through my research I see that MVVM should do the magic. But all the tutorial I found were doing the data-biding through the xaml file which can't be use in my case.
So I have 2 questions:
First: How is use the UIElementCollection of the Canvas? (is there a hidden method called who draws it, like Paint or Repaint in JAVA?)
Second: If I want to follow the MVVM pattern can I consider the MainPage as my View, the Graphics class as my ViewModel and the UIElement as my Model?
This is a really basic example but should get you going in the correct direction.
Graphics.cs
public class Graphics
{
public ObservableCollection<UIElement> UIElements { get; set; }
int poisiton = 0;
private Timer backgroundTimer;
public Graphics()
{
this.UIElements = new ObservableCollection<UIElement>();
this.backgroundTimer = new Timer(new TimerCallback((timer) => {
Deployment.Current.Dispatcher.BeginInvoke(() => this.GenerateLine());
}), null, 2000, 3000);
}
private void GenerateLine()
{
Line line = new Line();
// Line attributes
line.Stroke = new SolidColorBrush(Colors.Purple);
line.StrokeThickness = 15;
Point point1 = new Point();
point1.X = this.poisiton;
point1.Y = this.poisiton;
Point point2 = new Point();
point2.X = this.poisiton;
point2.Y = this.poisiton + 30;
line.X1 = point1.X;
line.Y1 = point1.Y;
line.X2 = point2.X;
line.Y2 = point2.Y;
// Line attributes
this.poisiton += 10;
UIElements.Add(line);
}
}
MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var graphics = new Graphics();
this.ContentPanel.DataContext = graphics;
}
MainPage.xaml
<Canvas x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ItemsControl ItemsSource="{Binding UIElements}">
</ItemsControl>
</Canvas>
I hope this helps.
Related
I am creating some shapes from code behind dynamically and adding them to a Grid and further add the Grid to Canvas.
So when I double click on a shape I should be able to add some text which works fine. Now lets say I have two shapes on the Canvas and when I try to draw a line between these shapes for some reason the first shape gets pulled away to the bottom and the line starts from the middle of first shape.
I want the shape not to change the position and the line should start from the bottom of first shape. Please see the image for my problem.
Please help with your thoughts. Here is my code. Also I tried numerous posts eg: Getting the top left coordinates of a WPF UIElement.
But none of them seem to help.
private void CvsSurface_OnDrop(object sender, DragEventArgs e) //In this event I am creating a shape dynamically and adding to a grid which is then added to a canvas.
{
Shape result = null;
Object droppedData = e.Data; //This part is not important
/*Translate Drop Point in reference to Stack Panel*/
Point dropPoint = e.GetPosition(this.cvsSurface);
//Console.WriteLine(dropPoint);
//Label lbl = new Label();
//lbl.Content = draggedItem.Content;
UIElement element = draggedItem.Content as UIElement;
Shape s = element as Shape;
if (s is Ellipse)
{
Ellipse ellipse = new Ellipse()
{
Height = s.Height,
Width = s.Width,
Fill = s.Fill
};
result = ellipse;
}
else if (s is Rectangle)
{
Rectangle rectangle = new Rectangle()
{
Height = s.Height,
Width = s.Width,
Fill = s.Fill
};
result = rectangle;
}
Grid sp = new Grid();
sp.Children.Add(result);
sp.MouseLeftButtonDown += Sp_MouseLeftButtonDown;
sp.MouseLeftButtonUp += Sp_MouseLeftButtonUp;
//sp.PreviewMouseLeftButtonUp += Sp_PreviewMouseLeftButtonUp;
//sp.MouseLeftButtonUp += Sp_MouseLeftButtonUp;
cvsSurface.Children.Add(sp);
Canvas.SetLeft(sp, dropPoint.X);
Canvas.SetTop(sp, dropPoint.Y);
}
private void Sp_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) // The purpose of this event lets say when some one clicks on a shape and drags the mouse to the other shape and when mouse up I want to draw a line between the shapes.
{
bool mouserelease = System.Windows.Input.Mouse.LeftButton == MouseButtonState.Pressed;
if (!mouserelease)
{
x2 = e.GetPosition(stackpanel).X;
y2 = e.GetPosition(stackpanel).Y;
Line l = new Line();
l.X1 = x1;
l.Y1 = y1;
l.X2 = x2;
l.Y2 = y2;
l.Margin = new Thickness(0, 19, 0, 0);
l.Stroke = new SolidColorBrush(Colors.Black);
l.StrokeThickness = 2;
stackpanel.Children.Add(l);
}
}
private void Sp_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) //This method lets say if user clicks twice then he wants to add some text or if he single clicks then I am assuming he is trying to a drag and draw a line
{
stackpanel = sender as Grid; //Sorry, the stackpanel is a global variable name of type Grid. Its actually Grid stackpanel;
if (e.ClickCount == 2)
{
dialog = new UserDialog()
{
DataContext = this,
Height = 180,
Width = 400,
MaxHeight = 180,
MaxWidth = 400
};
dialog.ShowDialog();
}
else
{
x1 = e.GetPosition(stackpanel).X + 18;
y1 = e.GetPosition(stackpanel).Y + 18;
//x1 = GetPosition(stackpanel, cvsSurface).X;
//y1 = GetPosition(stackpanel, cvsSurface).Y;
}
}
I want to implement class that draw grid on Canvas, but problem is that ActualWidth and ActualHeight are set to 0.
Canvas is all this gray area.
Can someone tell me what I'm doing wrong, I'm newbie in C#. Thanks!
class ImageGrid : Canvas
{
private double GridSize = 50;
public ImageGrid()
{
Background = new SolidColorBrush(Colors.DarkGray);
}
public void DrawGrid()
{
Children.Clear();
#region Draw image
// TODO - Draw image
#endregion
#region Draw grid
for ( double x = 0; x < ActualWidth; x+=GridSize )
{
Line line = new Line();
line.X1 = x;
line.X2 = x;
line.Y1 = 0;
line.Y2 = ActualWidth; // Why 0 ?
line.Stroke = Brushes.Black;
line.StrokeThickness = 1;
Children.Add(line);
}
for ( double y = 0; y < ActualHeight; y += GridSize )
{
Line line = new Line();
line.X1 = 0;
line.X2 = ActualHeight; // Why 0 ?
line.Y1 = y;
line.Y2 = y;
line.Stroke = Brushes.Black;
line.StrokeThickness = 1;
Children.Add(line);
}
#endregion
}
}
ImageGrid call:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ImageGrid ig = new ImageGrid();
Grid.Children.Add(ig);
ig.DrawGrid();
}
}
You need to wait for the layout pass before getting the ActualHeight/Width, try calling ig.DrawGrid in the Loaded handler.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ImageGrid ig = new ImageGrid();
Grid.Children.Add(ig);
ig.Loaded += ImageGrid_Loaded;
}
void ImageGrid_Loaded(object sender, RoutedEventArgs e)
{
ig.DrawGrid();
}
}
Note from FrameWorkElement.ActualHeight
This property is a calculated value based on other height inputs, and the layout system. The value is set by the layout system itself, based on an actual rendering pass, and may therefore lag slightly behind the set value of properties such as Height that are the basis of the input change.
Because ActualHeight is a calculated value, you should be aware that there could be multiple or incremental reported changes to it as a result of various operations by the layout system. The layout system may be calculating required measure space for child elements, constraints by the parent element, and so on.
I know how to get the cursor's position :
int X = Cursor.Position.X;
int Y = Cursor.Position.Y;
But this is relative to the screen. How do i get the coordinates relative to my Form?
Use the Control.PointToClient method. Assuming this points to the form in question:
var relativePoint = this.PointToClient(new Point(X, Y));
Or simply:
var relativePoint = this.PointToClient(Cursor.Position);
I would use PointToClient like this:
Point p = yourForm.PointToClient(Cursor.Position);
//if calling it in yourForm class, just replace yourForm with this or simply remove it.
How about trying like this using the Control.PointToClient:-
public Form()
{
InitializeComponent();
panel = new System.Windows.Forms.Panel();
panel.Location = new System.Drawing.Point(90, 150);
panel.Size = new System.Drawing.Size(200, 100);
panel.Click += new System.EventHandler(this.panel_Click);
this.Controls.Add(this.panel);
}
private void panel_Click(object sender, EventArgs e)
{
Point point = panel.PointToClient(Cursor.Position);
MessageBox.Show(point.ToString());
}
I have class, that creates Shapes for me (I tried to create some kind of "class factory" but im not sure if this is correct term for that I have created.
Problem is described in comments in my code.
public static Ellipse SomeCircle()
{
Ellipse e = new Ellipse();
double size = 10;
e.Height = size;
e.Width = size;
e.Fill = new SolidColorBrush(Colors.Orange);
e.Fill.Opacity = 0.8;
e.Stroke = new SolidColorBrush(Colors.Black);
// i want to have something like this here:
// canvas1.Children.Add(e);
// but I cant access non-static canvas1 from here
// I need this to place my ellipse in desired place
// (line below will not work if my Ellipse is not placed on canvas
// e.Margin = new Thickness(p.X - e.Width * 2, p.Y - e.Height * 2, 0, 0);
return e;
}
I have no idea how to workaround this.
I don't want to pass that canvas by parameter in my whole application...
Since you do not want to pass your Canvas around as a parameter, you could try creating an Extension Method which would act on your Canvas Object.
namespace CustomExtensions
{
public static class Shapes
{
public static Ellipse SomeCircle(this Canvas dest)
{
Ellipse e = new Ellipse();
double size = 10;
e.Height = size;
e.Width = size;
e.Fill = new SolidColorBrush(Colors.Orange);
e.Fill.Opacity = 0.8;
e.Stroke = new SolidColorBrush(Colors.Black);
dest.Children.Add(e);
return e;
}
}
}
Usage remember to add the CustomExtensions Namespace to your usings.
canvas1.SomeCircle();
Here the scenario is:
I have a canvas with different diagrams drawn on it. Now the requirement is to zoom into the canvas using the code behind either using C# or VB. Moreover I need to place the zoom code in some dll so that i can reuse the same set of code through out my application.
Now my question is how to do this....
I have tried the following code pls have a look..
public MainWindow()
{
InitializeComponent();
canvas.MouseEnter += new MouseEventHandler(canvas_MouseEnter);
canvas.MouseWheel += new MouseWheelEventHandler(canvas_MouseWheel);
}
void canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
double height = canvas.ActualHeight;
double width = canvas.ActualWidth;
double zoom = e.Delta;
height += 2;
width += 2;
ScaleTransform sc = new ScaleTransform(width, height);
canvas.LayoutTransform = sc;
canvas.UpdateLayout();
}
Try to implement this sample:
var canvas = new Canvas();
var st = new ScaleTransform();
var textBox = new TextBox {Text = "Test"};
canvas.RenderTransform = st;
canvas.Children.Add(textBox);
canvas.MouseWheel += (sender, e) =>
{
if (e.Delta > 0)
{
st.ScaleX *= 2;
st.ScaleY *= 2;
}
else
{
st.ScaleX /= 2;
st.ScaleY /= 2;
}
};
I believe what you are looking for is a zoom behavior. Behaviors are objects that encapsulate some form of interactive behavior. I've seen several examples of "Zoom Behaviors" that you should be able to use for your project. You should be able to use or modify one of the following...
Laurent Bugnion's Zoom Behavior
WPF Extensions - has a zoom control