A curve has to stay within a matrix - c#

I have a matrix with an uneven number of columns per row. The number of columns decreases with increasing rows. Now there is a curve drawn on top of the matrix.
A requirement is, that the curve has to be completely within the matrix.
The picture is a matrix where the curve leaves the boundaries of the matrix. This is what I have to fix.
The algorithm that calculates the curve is not aware of the size of the matrix.
The question is: What is the best way to fulfill this requirement?
Will the algorithm that calculates the curve have to change to be aware of the matrix dimensions?
Or can I change the curve afterwards to "live" inside the matrix?
What would you recommend?
The expected curve would look like this:
I added blue dots to the points that make up the polyline. The little black dots on the line represent calculated points based on linear interpolation. Each of these calculated Points can be tested to determine whether they are inside or outside of the matrix. The actual matrix has more columns and rows but displaying it does not add to the clarity of the task at hand or the possible approach to solve it.
I created a little demo:
MainWindows.xaml
<Window x:Class="InsideMatrix.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:InsideMatrix"
Title="MainWindow" Height="250" Width="525" x:Name="Main">
<Grid>
<local:Matrix/>
<Polyline Stroke="Red" StrokeThickness="3" Points="{Binding Curve, ElementName=Main}" Visibility="Visible"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Media;
namespace InsideMatrix
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private PointCollection curve;
public MainWindow()
{
InitializeComponent();
if (!DesignerProperties.GetIsInDesignMode(this))
{
this.Curve = this.CalculateCurve();
}
}
private PointCollection CalculateCurve()
{
// This is just a stub for an algorith that will calculate a curve
return new PointCollection()
{
new Point(57, 24),
new Point(114, 48),
new Point(171, 72),
new Point(228, 96),
new Point(456, 153)
};
}
public PointCollection Curve
{
get { return this.curve; }
set
{
this.curve = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Matrix.xaml
<UserControl x:Class="InsideMatrix.Matrix"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:insideMatrix="clr-namespace:InsideMatrix"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
</Grid>
</UserControl>
Matrix.xaml.cs
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace InsideMatrix
{
public partial class Matrix : UserControl
{
private int width = 57;
private int height = 24;
private int[] cellsPerRow = { 7, 7, 4, 4, 2, 2 };
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (DesignerProperties.GetIsInDesignMode(this)) return;
Pen pen = new Pen(new SolidColorBrush(Colors.Black), 1);
int row = 0;
foreach (var cells in cellsPerRow)
{
row++;
for (int column = 7; column > 7 - cells; column--)
{
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Transparent), pen, new Rect(column * width, row * height, width, height));
}
}
}
}
}

Related

C# - WPF how to get object layer level [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
My goal is : Only allowed click to remove a rectangle if there is no rectangle in front of the target rectangle.
Maybe there is a solution to get a layer level from an object ? I have found nothing about this, im new to WPF. So if some can explain the solution or the way to think about this problem.
Code xaml.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace WpfApp1
{
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
DispatcherTimer gameTimer = new DispatcherTimer();
List<Rectangle> removeThis = new List<Rectangle>();
int posX;
int posY;
int width;
int rectangleVariation;
int height;
Random rand = new Random();
Brush brush;
public MainWindow()
{
InitializeComponent();
Random rand = new Random();
int nbObjects = rand.Next(15,30);
for (int i = 0; i < nbObjects; i++)
{
brush = new SolidColorBrush(Color.FromRgb((byte)rand.Next(1, 255), (byte)rand.Next(1, 255), (byte)rand.Next(1, 255)));
posX = rand.Next(15, 700);
posY = rand.Next(50, 250);
rectangleVariation = rand.Next(0, 2);
if (rectangleVariation == 0)
{
width = 200;
height = 50;
}
else
{
width = 50;
height = 200;
}
Rectangle rectangle = new Rectangle
{
Tag = "rectangle",
Height = height,
Width = width,
Stroke = Brushes.Black,
StrokeThickness = 1,
Fill = brush
};
Canvas.SetLeft(rectangle, posX);
Canvas.SetTop(rectangle, posY);
MyCanvas.Children.Add(rectangle);
}
}
private void ClickOnCanvas(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Rectangle)
{
Rectangle rectangle = (Rectangle)e.OriginalSource;
MyCanvas.Children.Remove(rectangle);
}
}
}
}
Code xaml file :
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Canvas Name="MyCanvas" MouseLeftButtonDown="ClickOnCanvas" Background="DarkGray">
</Canvas>
</Window>
You should first determine whether the clicked Rectangle intersects with any other Rectangle elements and if it does, you could determine whether it's in top of all of them by looking at the index in the Canvas's Children collection.
The Rect type has an IntersectsWith method that you can use. Something like this:
private void ClickOnCanvas(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Rectangle)
{
Rectangle clickedRectangle = (Rectangle)e.OriginalSource;
Rect clickedRect = new Rect()
{
Location = new Point(Canvas.GetLeft(clickedRectangle), Canvas.GetTop(clickedRectangle)),
Size = new Size(clickedRectangle.Width, clickedRectangle.Height)
};
for (int i = 0; i < MyCanvas.Children.Count; i++)
{
if (MyCanvas.Children[i] is Rectangle rectangle && rectangle != clickedRectangle)
{
Rect rect = new Rect()
{
Location = new Point(Canvas.GetLeft(rectangle), Canvas.GetTop(rectangle)),
Size = new Size(rectangle.Width, rectangle.Height)
};
if (clickedRect.IntersectsWith(rect) && MyCanvas.Children.IndexOf(clickedRectangle) < i)
return;
}
}
MyCanvas.Children.Remove(clickedRectangle);
}
}

Placing an object randomly depending on window size

I'm trying to place an object on a random location depending on the window size.
LayoutRoot is the name of the grid it's placed in.
//Give Dot a random position
int left = random.Next(LayoutRoot.MinWidth, LayoutRoot.MaxWidth);
int top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
Error on LayoutRoot.MinWidth & MaxWidth: Cannot convert double to int
tried
//Give Dot a random position
double left = random.NextDouble(LayoutRoot.MinWidth, LayoutRoot.MaxWidth);
double top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
Error on NextDouble: Method NextDouble takes 2 arguments
Ok going to up this one up a bit. Modified your code. This code doesn't assume any predefined height or width, it gets that based on the current size of the LayoutRoot. It also offsets the Dot size so it doesn't fall off the screen.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="LayoutRoot">
<Ellipse Width="2" Height="2" Fill="Black" x:Name="Dot"></Ellipse>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow
{
private static Random random = new Random();
public MainWindow()
{
InitializeComponent();
// Don't move dot until the window is loaded, not necessary but generally you don't want to hold up the window from displaying.
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
// The farthest left the dot can be
double minLeft = 0;
// The farthest right the dot can be without it going off the screen
double maxLeft = LayoutRoot.ActualWidth - Dot.Width;
// The farthest up the dot can be
double minTop = 0;
// The farthest down the dot can be without it going off the screen
double maxTop = LayoutRoot.ActualHeight - Dot.Height;
double left = RandomBetween(minLeft, maxLeft);
double top = RandomBetween(minTop, maxTop);
Dot.Margin = new Thickness(left, top, 0, 0);
}
private double RandomBetween(double min, double max)
{
return random.NextDouble() * (max - min) + min;
}
}
}
this might work.
double mWidth = LayoutRoot.MinWidth;
double mxWidth = LayoutRoot.MaxWidth;
double left = random.NextDouble(mWidth, mxWidth);
double top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
}
So after looking at the answer given by Kelly I had a similar issue where the object would not display after loading. My goal was to have the welcome screen of an application randomly move every 10 seconds. So following his design there was an issue where because the grid did not have an actual length due to the lack of column/row definitions we have to force the app to recalculate size at every attempt to randomize.
Here is the XAML I used:
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="WelcomeTextBlock"
Height="200"
Style="{StaticResource WelcomeTextBlock}">
Welcome!
</TextBlock>
<Ellipse Width="2" Height="2" x:Name="Dot"/>
</Grid>
And more importantly the actual code-behind:
public partial class WelcomePage : Page
{
private static Random Rnd = new Random();
private static DispatcherTimer _timer;
public WelcomePage()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnClosing;
}
private void OnLoaded(object sender, RoutedEventArgs args)
{
this.LayoutRoot.Margin = GetNewMargin();
_timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(5)};
_timer.Tick += MoveWelcome;
_timer.Start();
}
private void MoveWelcome(object sender, EventArgs e)
{
this.LayoutRoot.Margin = GetNewMargin();
}
private Thickness GetNewMargin()
{
this.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.Arrange(new Rect(0, 0, this.DesiredSize.Width, this.DesiredSize.Height));
var maxLeft = this.LayoutRoot.ActualWidth - Dot.Width;
var maxTop = LayoutRoot.ActualHeight - Dot.Height;
var left = RandomBetween(0, maxLeft);
var top = RandomBetween(0, maxTop);
return new Thickness(left, top, 0 ,0);
}
private static void OnClosing(object sender, RoutedEventArgs args)
{
_timer.Stop();
}
private static double RandomBetween(double min, double max) => Rnd.NextDouble() * (max - min) + max;
Besides the timer functionality the major change here is that when the margin thickness is being calculated we force the window, or in my case page to update the measurements of the screen and arrange the items correctly.

WPF: Connecting rectangles on canvas dynamically with path between

This is my first time posting, so be easy on me...
There are definitely posts that show how to connect lines to blocks, but this one is slightly different. The grids are placed on the canvas dynamically. I want a line to connect the grid from where the button is pressed to the new grid that's placed on the canvas. However the below code doesn't work. I've struggled with this one for a very long time.
Solely by accident I discovered a Messagebox thrown in the button event handler will allow it to work. I figured this was from threads running at different times, but after messing with task.delay/thread.sleep/async/await I can't find the solution.
I'm using custom classes because this is a stripped down version of the larger program, I wanted to have similar functionality in my example to reflect possible errors but leave out the unnecessary pieces.
I'm using this as a last resort, thank you in advance for any help you can provide. First the CS code
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Grid1 g = myCanvas.CreateGrid();
ContentControl1 cc = myCanvas.CreateCC();
Button1 b = myCanvas.CreateButton1();
Grid1.SetColumn(cc, 0);
Grid1.SetRow(cc, 0);
Grid1.SetColumn(b, 1);
Grid1.SetRow(b, 1);
g.Children.Add(cc);
g.Children.Add(b);
Canvas1.SetLeft(g, 500);
Canvas1.SetTop(g, 5);
myCanvas.Children.Add(g);
}
}
public class Button1 : Button
{
protected override void OnClick()
{
Grid1 old_g = (Grid1)VisualTreeHelper.GetParent(this as DependencyObject);
Canvas1 cnv = (Canvas1)VisualTreeHelper.GetParent(old_g as DependencyObject);
Grid1 g = cnv.CreateGrid();
ContentControl1 cc = cnv.CreateCC();
Button1 b = cnv.CreateButton1();
Grid1.SetColumn(cc, 0);
Grid1.SetRow(cc, 0);
Grid1.SetColumn(b, 1);
Grid1.SetRow(b, 1);
g.Children.Add(cc);
g.Children.Add(b);
Canvas1.SetLeft(g, 500);
Canvas1.SetTop(g, cnv.Children.Count * 120);
cnv.Children.Add(g);
cnv.ConnectGrids(old_g, g);
}
}
public class Canvas1 : Canvas
{
public Grid1 CreateGrid()
{
Grid1 g = new Grid1() { Width = 100, Height = 20, Background = Brushes.White };
g.HorizontalAlignment = HorizontalAlignment.Center;
g.VerticalAlignment = VerticalAlignment.Center;
g.ShowGridLines = false;
ColumnDefinition colDef1 = new ColumnDefinition();
ColumnDefinition colDef2 = new ColumnDefinition() { Width = new GridLength(20) };
g.ColumnDefinitions.Add(colDef1);
g.ColumnDefinitions.Add(colDef2);
RowDefinition rowDef1 = new RowDefinition();
g.RowDefinitions.Add(rowDef1);
return g;
}
public ContentControl1 CreateCC()
{
ContentControl1 cc = new ContentControl1()
{
VerticalContentAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
BorderBrush = Brushes.BlueViolet,
};
return cc;
}
public Button1 CreateButton1()
{
Button1 b = new Button1()
{
VerticalContentAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
BorderBrush = Brushes.Red
};
return b;
}
public void ConnectGrids(Grid1 g1, Grid1 g2)
{
Canvas1 cnv = (Canvas1)VisualTreeHelper.GetParent(g1 as DependencyObject);
Transform transform1 = (Transform)g1.TransformToVisual(cnv as Visual);
Transform transform2 = (Transform)g2.TransformToVisual(cnv as Visual);
Point StartPoint1 = transform1.Transform(new Point(g1.Width, g1.Height / 2.0));
Point EndPoint1 = transform2.Transform(new Point(g2.Width, g2.Height / 2.0));
var lineGeometry = new LineGeometry()
{
StartPoint = StartPoint1,
EndPoint = EndPoint1
};
var path = new Path()
{
Data = lineGeometry,
Stroke = new SolidColorBrush(Colors.Black),
};
cnv.Children.Add(path);
}
}
public class ContentControl1 : ContentControl
{
}
public class Grid1 : Grid
{
}
}
then the xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:WpfApplication1"
Title="MainWindow" Height="1000" Width="1000">
<DockPanel>
<Button DockPanel.Dock="Top" Width="100" Height="20" Content="Start" Click="Button_Click"/>
<ScrollViewer HorizontalScrollBarVisibility="Visible" Width="901" x:Name="_scrollViewer" Margin="0,5,0,25">
<local:Canvas1 Background="Gray" Height="1000" Width="1000" x:Name="myCanvas"/>
</ScrollViewer>
</DockPanel>
</Window>
How can I successfully connect the grid with the path?
I love that I solved this/hate that I have to answer my own dumb question...
Sooo simple. UpdateLayout(). The objects don't change location until it has been rendered, so the line was going to its old location.

Creating WriteableBitmap form chart issue

I have a problem when trying to create writeable bitmap form Silverlight toolkit Graph.
When using textBlock, everything is fine, but after trying to use Chart, generated bitmap is empty :( .
var data = new List<Point>(100);
for (int i = 0; i < 100; i++)
{
data.Add(new Point(i, Math.Sin(i * Math.PI / 50)));
}
Chart chart_ = new Chart()
{
Name = "Chart",
Width = 512,
Height = 512
};
LineSeries line = new LineSeries()
{
Name = "Line",
Title = "test",
IndependentValuePath = "X",
DependentValuePath = "Y",
ItemsSource = data
};
chart_.Series.Add(line);
This code creates chart with sinusoid in it. Then Im trying to create bitmap from it.
LayoutRoot.Children.Add(chart_); // I tried to add chart_ to visual tree, It doesn't help
//creates bitmap
ScaleTransform t = new ScaleTransform() { ScaleX = 1.0, ScaleY = 1.0 };
//bitmap = new WriteableBitmap(chart_, t); Tried it also with this way
bitmap = new WriteableBitmap(512, 512);
bitmap.Render(chart_, t);
texture = new Texture2D(GraphicsDeviceManager.Current.GraphicsDevice, bitmap.PixelWidth, bitmap.PixelHeight, false, SurfaceFormat.Color);
bitmap.CopyTo(texture);
All this code creates Empty Bitmap.But when I use TextBlock or some primitives like Ellipse, everything works. Im sure, that code generating chart is fine, cause chart is generated fine in Silverlight control.
EDIT:
I tried to create bitmap this way, but it dont help.
chart_.InvalidateMeasure();
bitmap = new WriteableBitmap(512, 512);
bitmap.Render(chart_, null);
bitmap.Invalidate();
EDIT 2:
I don't want graph to be in visual tree. I just need generate image of it an than use it in XNA part of my application.
Just a couple of small changes, but I suspect the big change is manually adding a reference to System.Windows.Controls after using NuGet to add the Charting package.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Controls.DataVisualization.Charting;
using System.Windows.Media.Imaging;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
Chart chart_;
public MainPage()
{
InitializeComponent();
var data = new List<Point>(100);
for (int i = 0; i < 100; i++)
{
data.Add(new Point(i, Math.Sin(i * Math.PI / 50)));
}
chart_ = new Chart()
{
Name = "Chart",
Width = 512,
Height = 512
};
LineSeries line = new LineSeries()
{
Name = "Line",
Title = "test",
IndependentValuePath = "X",
DependentValuePath = "Y",
ItemsSource = data
};
chart_.Series.Add(line);
LayoutRoot.Children.Add(chart_); // I tried to add chart_ to visual tree, It doesn't help
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
//creates bitmap
WriteableBitmap bitmap;
ScaleTransform t = new ScaleTransform() { ScaleX = 1.0, ScaleY = 1.0 };
//bitmap = new WriteableBitmap(chart_, t); Tried it also with this way
bitmap = new WriteableBitmap(512, 512);
bitmap.Render(chart_, t);
//texture = new Texture2D(GraphicsDeviceManager.Current.GraphicsDevice, bitmap.PixelWidth, bitmap.PixelHeight, false, SurfaceFormat.Color);
//bitmap.CopyTo(texture);
image1.Source = bitmap;
}
}
}
And
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<Image Height="150" HorizontalAlignment="Left" Margin="97,109,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="200" />
</Grid>
</UserControl>
That should get you going...
Don't forget to delete all of the attempted workarounds - I don't think any of them are necessary. The only code I did not use from your example had to do with the textures - I didn't know what to do with that and it wasn't the problem you were having anyways...
David
I believe that by the time you are creating WriteableBitmap object & calling Render method, Chart has not be rendered yet. You can check this by moving these lines of code to some other event like Button_Click etc. Have a look at below codes as it is creating the WriteableBitmap and then passes it to image control as source....
XAML Code.....
<UserControl x:Class="TryBitmap.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="25" Width="100" Content="Capture" Click="Button_Click"/>
<Grid x:Name="grdGraphs" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="img" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Grid>
Code Behind....
public partial class MainPage : UserControl
{
private Chart chart_;
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var data = new List<Point>(100);
for (int i = 0; i < 100; i++)
{
data.Add(new Point(i, Math.Sin(i * Math.PI / 50)));
}
chart_ = new Chart()
{
Name = "Chart",
Width = 512,
Height = 512
};
LineSeries line = new LineSeries()
{
Name = "Line",
Title = "test",
IndependentValuePath = "X",
DependentValuePath = "Y",
ItemsSource = data
};
chart_.Series.Add(line);
chart_.SetValue(Grid.ColumnProperty, 0);
grdGraphs.Children.Add(chart_);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var bitmap = new WriteableBitmap((int)(chart_.RenderSize.Width), (int)(chart_.RenderSize.Height));
bitmap.Render(chart_, new MatrixTransform());
bitmap.Invalidate();
img.Source = bitmap;
}
}
I'm afraid this is only going to be half an answer. I can solve your immediate problem but I'm afraid you'll just end up with another one I haven't managed to solve.
Creating the bitmap after a call to Dispatcher.BeginInvoke should ensure that your bitmap isn't completely blank, i.e.:
// do stuff with chart_ ...
Dispatcher.BeginInvoke(() =>
{
bitmap = new WriteableBitmap(512, 512);
bitmap.Render(chart_, null);
bitmap.Invalidate();
// At this point, the bitmap shouldn't be blank.
});
However, the results of this likely to be less than satisfactory. I ran your code and I found that the LineSeries was missing from the chart image, although the rest of the chart was there. This remained true even after I set all Durations of the various animations in the ControlTemplate for the LineSeries to 0 and additionally set the following properties on the chart:
chart_.AnimationSequence = AnimationSequence.Simultaneous;
chart_.TransitionDuration = new TimeSpan(0);
I tried wrapping the WriteableBitmap operations in further calls to Dispatcher.BeginInvoke(), and after doing this the data points started to appear more clearly. However, I can't believe that an approach like this is the right solution to the problem.

Drawing circles in WPF

I am trying to write a WPF application where you can draw circles on a window by double clicking it. So far I have this code:
public class ShapeAdorner : Adorner
{
private readonly Ellipse _circle;
public ShapeAdorner(UIElement adornedElement, Point circleCenter)
: base(adornedElement)
{
_circle = new Ellipse
{
Width = 10,
Height = 10,
Stroke = Brushes.Black,
StrokeThickness = 1.5
};
_circle.Margin =
new Thickness(left: circleCenter.X, top: circleCenter.Y, right: 0, bottom: 0);
base.AddVisualChild(_circle);
}
protected override Size ArrangeOverride(Size finalSize)
{
_circle.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Size MeasureOverride(Size constraint)
{
_circle.Measure(constraint);
return constraint;
}
protected override Visual GetVisualChild(int index)
{
return _circle;
}
protected override int VisualChildrenCount
{
get { return 1; }
}
}
Here's the client code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(myLabel);
adornerLayer.Add(new ShapeAdorner(adornedElement: myLabel, circleCenter: e.GetPosition(myLabel)));
}
}
The circles are supposed to be centered at the point where you double click the window; however, the circles drawn by the code above are centered below and to the right of "the double click point". How can this be fixed?
EDIT: myLabel has Height=350 and Width=525. Let's say that I double click the point (X,Y); then the circle gets plotted at ((350+X)/2,(525+Y)/2).
EDIT 2: Just for completeness, here's the .xaml file:
<Window x:Class="Adorners.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Adorners project" Height="350" Width="525" MouseDoubleClick="Window_MouseDoubleClick">
<Grid>
<Label Name="myLabel" Content="my label" Background="Red"></Label>
</Grid>
</Window>
Where you set the margin you have to subtract the radius from the top and left properties to offset the circle.
You'll need to offset by half the width/height of the circle. Hard-coded here to make it easy to follow:
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(myLabel);
var point = e.GetPosition(myLabel);
point.X -= 5;
point.Y -= 5;
adornerLayer.Add(new ShapeAdorner(myLabel, point));
You need to take the witdth and height into conserderation when you're setting the margin. the top should be the centerposition minus half of the height, and the same for the left:
new Thickness(
left: circleCenter.X + (_circle.Width/2), //farther to the right
top: circleCenter.Y - (_circle.Height/2), //higher up
right: 0, bottom: 0);
The previous answers are correct. However, the main problem was that I had omitted these two lines:
_circle.HorizontalAlignment = HorizontalAlignment.Left;
_circle.VerticalAlignment = VerticalAlignment.Top;
The default value Stretch caused a huge offset error.

Categories

Resources