This is my code to draw a maximum of 3 ellipses on a drawing canvas, I would like to have properties to describe each object drawn (such as mass, velocity and id)
How do I create an id for each new object drawn in the loop so I can give each one different masses and velocities?
Ellipse ealswith = new Ellipse();
ealswith.Height = 70; // Setting height
ealswith.Width = 70; // setting width
for (int i = 0; i <= setOfEllipses.GetUpperBound(0); i++)
{
if (this.drawingCanvas.Children.Count > 2)
{
MessageBox.Show("Maximum number of objects have been drawn.");
break;
}
if (setOfEllipses[i] == null)
{
if (this.drawingCanvas.Children.Count < 3)
{
setOfEllipses[i] = ealswith;
drawingCanvas.Children.Add(ealswith);
break;
}
}
}
I can think of two ways to do this:-
Use the Tag property of the ellipse to store the ID, or better still, create a class that contins the information you need (mass, velocity, etc) and store that object in the Tag property.
Extend the Ellipse class and the extra properties as derived members.
Related
Shape shape = sm.maakEllips();
if (!canvas.Children.Contains(shape))
{
cm.Draw(shape, canvas, locatie);
}
public void Draw(Shape vorm, Canvas canvas, Point locatie)
{
if (vorm.Height <= canvas.Height && vorm.Width <= canvas.Width)
{
Canvas.SetTop(vorm, locatie.Y);
Canvas.SetLeft(vorm, locatie.X);
canvas.Children.Add(vorm);
}
}
So I add a shape to a canvas in the Draw(). Then when I check on this in the upper if clause, I'm still able to add the same shape to the same canvas multiple times.
I don't get it, what am I doing wrong?
EDIT:
Shape shape = sm.makeShape(Convert.ToByte(textboxR.Text), Convert.ToByte(textboxG.Text), Convert.ToByte(textboxB.Text), Convert.ToInt32(textboxHoogte.Text), Convert.ToInt32(textboxBreedte.Text));
foreach (Shape existingShape in canvas.Children.OfType<Shape>())
{
if (existingShape.Width != shape.Width && existingShape.Height != shape.Height
&& existingShape.Fill != shape.Fill)
{
cm.Draw(shape, canvas, locatie);
}
}
I tried this and now I'm not even able to add a shape to the canvas at all.
I don't see what I'm doing wrong at all.
Your Draw() method add vorm of type Shape to the canvas specified in canvas. And I assume your sm.maakEllips() returns an ellipse.
Therefore, when you run the following code:
Shape shape = sm.maakEllips();
if (!canvas.Children.Contains(shape))
{
cm.Draw(shape, canvas, locatie);
}
You will go inside the if statement only if the canvas contains that exact shape object you created in the line above, using sm.maakEllips() method. It cannot be any shape that has the same properties of the shape object above. Because, every time you create a new object, even with the exact same properties including its name, they are still two distinct objects in .NET world.
To illustrate the point see the code sample below.
Your unchanged Draw() method:
public void Draw(Shape vorm, Canvas canvas, Point locatie)
{
if (vorm.Height <= canvas.Height && vorm.Width <= canvas.Width)
{
Canvas.SetTop(vorm, locatie.Y);
Canvas.SetLeft(vorm, locatie.X);
canvas.Children.Add(vorm);
}
}
A makeEllipse() method that creates an ellipse of width and height of 100 and 80 respectively, and assigns the name passed in the parameter.
public Shape makeEllipse(string name)
{
Shape sh = new Ellipse
{
Name = name,
Width = 100,
Height = 80,
};
return sh;
}
Now see the following code, executed at the click of a button.
private void btnGO_Click(object sender, RoutedEventArgs e)
{
// Creates an ellipse with name "Shape1", and assigns to sh1.
Shape sh1 = makeEllipse("Shape1");
// Adds the said sh1 to the canvas using `Draw()` method.
Draw(sh1, myCanvas, new Point(5, 5));
// See if sh1 exists as a child of `myCanvas`.
// Since sh1 is now a child of canvas, code does NOT go inside the if-clause.
if (!myCanvas.Children.Contains(sh1))
{
Draw(sh1, myCanvas, new Point(5, 5));
}
// Creates an ellipse with the same name "Shape1", and assigns to sh2.
Shape sh2 = makeEllipse("Shape1");
// It is NOT added to the canvas using `Draw()` method.
// Now, here, code DOES go inside the if-clause, because the said object does not exist as a child of `myCanvas`.
if (!myCanvas.Children.Contains(sh2))
{
Draw(sh2, myCanvas, new Point(5, 5));
}
}
Comments above should be good enough, but to explain again,
When you create sh1 and adds it to myCanvas using Draw() method, it becomes a child element of myCanvas.Children.
Then, when you check if it is a child using if (!myCanvas.Children.Contains(sh1)), since it IS a child element by that time, condition becomes false and we do not go inside the if clause.
Next, we create sh2, which has the exact same dimensions and the name as sh1. However, and this is the key, .NET treats it as a different object even though it has the same properties as the previous object. Reason being, whenever we use the new keyword, .NET creates an actual new object.
Afterwards, we DON'T add it to the canvas using Draw() method.
Now, at the second if when we check if myCanvas contains the object, it finds that sh2 is NOT a child of myCanvas, so it goes inside the if clause.
maakEllips() always creates a new Shape. If you want to compare this one against other Shape elements in the Canvas, you need to iterate through these. The following code compares the height, width and position and adds the new Shape to the Canvas if any of them differ:
Shape shape = sm.maakEllips();
foreach (Shape existingShape in canvas.Children.OfType<Shape>())
{
if(existingShape.Width != canvas.Width || existingShape.Height != canvas.Height
|| Canvas.GetLeft(existingShape) != Canvas.GetLeft(shape)
|| Canvas.GetTop(existingShape) != Canvas.GetTop(shape))
{
cm.Draw(shape, canvas, locatie);
}
}
I am an hobbyist .NET developer. I am developing a Windows Forms app and this picture shows want I want to use a chart control for:
Basically, I want to change the upper part of every column which is taller than a certain threshold (400 in the example above). I have searched around a bit, but no joy yet. Any ideas?
EDIT: I have a graceful workaround, but it's still frustrating not to have something available in the control itself. I have created two series, red and blue. When I add a point I check if the height is bigger than the threshold. If yes, I add the point to the red series, and I also add a point of threshold height to the blue series. If not, I add it to the blue series as is. The code can explain better than me:
private void AddPointToChart(Chart chart, int x, int y)
{
if (this.threshold < y)
{
chart.Series[1].Points.AddXY(x, y);
y = this.threshold;
}
chart.Series[0].Points.AddXY(x, y);
}
As per the original question, StackedColumn is the way to go!
And for your edit, on option (not an easy one) is to override virtual methods of Chart to add your own behaviour. I came up with an example after some experimentation:
public class ThresholdColumnChart : Chart
{
private double _threshold = 50d;
public double Threshold
{
get { return _threshold; }
set { _threshold = value; Invalidate(); }
}
public ThresholdColumnChart() : base() { }
protected override void OnCustomize()
{
base.OnCustomize();
if (Series.Count != 1)
return;
Series.Add(new Series());
foreach (var dataPoint in Series[0].Points)
{
var newDataPoint = new DataPoint();
newDataPoint.XValue = dataPoint.XValue;
newDataPoint.YValues[0] = (dataPoint.YValues[0] > _threshold ?
dataPoint.YValues[0] - _threshold : 0);
Series[1].Points.Add(newDataPoint);
if (dataPoint.YValues[0] > _threshold)
dataPoint.YValues[0] = _threshold;
}
Series[0].ChartType = SeriesChartType.StackedColumn;
Series[1].ChartType = SeriesChartType.StackedColumn;
Series[1].Color = Color.Red;
}
protected override void OnPostPaint(ChartPaintEventArgs e)
{
base.OnPostPaint(e);
if (!(e.ChartElement is ThresholdBarChart))
return;
if (Series.Count != 2)
return;
for (int i = 0; i < Series[0].Points.Count; i++)
Series[0].Points[i].YValues[0] += Series[1].Points[i].YValues[0];
Series.Remove(Series[1]);
}
}
In short the OnCustomize(...) method is called just before the chart is drawn (before OnPrePaint(...)) at which point it creates a second series to be used for drawing. The new series is constructed with the threshold in mind, and the first series is reduced not to exceed the threshold just like the example in your edit.
After the chart is done painting OnPostPaint(...) will be called and the first series will be restored to it's original values, and the "extra" series is removed.
Both OnPrePaint(...) and OnPostPaint are actually called multiple times when a chart is drawn, hence why I had to put if (!(e.ChartElement is ThresholdBarChart)) return; in; to make sure we only remove the series once the chart has finnished painting.
After adding the code and compiling once you will get a new usercontrol added to the design toolbox called "ThresholdColumnChart". You can set the threshold from the Property "Threshold" (also in design). I wouldnt trust this code with my life but it should serve as a starting point.
I use Chart to draw a graph with 2 lines. Now my aim is to set the LineColor of the MajorGrid of the second Y-axis to the color of the corresponding line. Here is my code:
public partial class Form1 : Form
{
List<double> values_1 = new List<double>();
List<double> values_2 = new List<double>();
public Form1()
{
InitializeComponent();
make_values();
for (int i = 0; i < values_1.Count; i++)
{
chart1.Series[0].Points.AddY(values_1[i]);
}
for (int i = 0; i < values_2.Count; i++)
{
chart1.Series[1].Points.AddY(values_2[i]);
}
// set the colour of grid to corresponding line
chart1.ChartAreas[0].AxisY2.MajorGrid.LineColor = chart1.Series[1].Color;
}
private void Form1_Load(object sender, EventArgs e)
{
}
public void make_values()
{
for (int i = 0; i < 600; i++)
{
values_1.Add(Math.Sin(i / 60.0));
values_2.Add(Math.Cos(i / 60.0));
}
}
}
Since the colours are chosen automatically for the 2 different series I though I could just grab the colour. But when debugging I see that the colour is (0,0,0):
So the grid colour does not change. But the colour of the second series is not (0,0,0) as can be seen when the window is loaded!:
If I force and set manually the colours of the 2 series before that. Everything works fine, and the grid gets the corresponding colour.
Does anyone know at which point in time I would have to grab the colour of the series to get the real value?
To access the Series Colors you need to call ApplyPaletteColors. This is necessary when you want to use them for other elements or when custom drawing. You should also call it again after changing the palette..
chart1.ApplyPaletteColors();
MSDN:
Remarks
When the Chart colors are automatically assigned at run time, there is
no way to know what the colors will be prior to the time when the
chart rendered; the Color property of an automatically assigned value
will return Empty at this time.
If you call the ApplyPaletteColors method, the colors for the series
and the data points will be set, which allows for programmatic access.
I have a matrix of bits 20x23.
I need to represent this matrix in a winform (GUI).
The idea is that the user will be able to change the content of specific cell by clicking the relevant button, that represent the specific cell in the matrix.
(When the user click a button, the relevant bit cell in the matrix is being inverted)
I have considered using GRID for this, but due to GUI (Design) issue, it is not possible to use it.
How can I create and manage 20x23 (=460) buttons effectively and keep it correlated to the real matrix ?
It is not that difficult, I would start with a method that will generate a button matrix for you. This matrix consists of Buttons, where the ID (ie. Tag) will correspond to the correct cellNumber (you might consider passing the coordinates as a Point instance as well, I will leave that up for you to decide).
So basically, it comes to this, where all the buttons are rendered on a panel (panel1):
...
#region Fields
//Dimensions for the matrix
private const int yDim = 20;
private const int xDim = 23;
#endregion
...
private void GenerateButtonMatrix()
{
Button[,] buttonMatrix = new Button[yDim, xDim];
InitializeMatrix(ref matrix); //Corresponds to the real matrix
int celNr = 1;
for (int y = 0; y < yDim; y++)
{
for (int x = 0; x < xDim; x++)
{
buttonMatrix[y,x] = new Button()
{
Width = Height = 20,
Text = matrix[y, x].ToString(),
Location = new Point( y * 20 + 10,
x * 20 + 10), // <-- You might want to tweak this
Parent = panel1,
};
buttonMatrix[y, x].Tag = celNr++;
buttonMatrix[y,x].Click += MatrixButtonClick;
}
}
}
As you can see, all 460 buttons have a custom EventHandler connected to the ClickEvent, called MatrixButtonClick(). This eventhandler will handle the ClickEvent and may determine on which button the user has clicked. By retrieving the tag again, you may calculate the correct coordinate which corresponds to the 'real' matrix.
private void MatrixButtonClick(object sender, EventArgs e)
{
if (sender is Button)
{
Button b = sender as Button;
//The tag contains the cellNr representing the cell in the real matrix
//To calculate the correct Y and X coordinate, use a division and modulo operation
//I'll leave that up to you :-)
.... Invert the real matrix cell value
}
}
I will not give away everything, since it is a nice practice for you to achieve :).
I would:
1) create an object with needed properties
2) fill a list and fill with values
3) iterate list creating buttons and assigning its click handler and button's name (for name something like button_rowindex_colindex)
4) inside click handler, assign value to object cell by detecting which button was clicked
In Dynamic Data Display, there is a class called MarkerPointsGraph. This derives from FrameWorkElement (through a series of other classes along the way, the most immediate parent being PointsGraphBase) and overrides the OnRenderMethod to draw a set of markers on the chart.
It does this by calling a render method in the appropriate marker type (say, Triangle, Circle and so on) once for each point rendered on the screen. I need to find a way to identify when the mouse hovers on one of these markers so that I can set the tooltip for that marker.
I have a set of methods that allow me to convert a point from the screen position to the viewport position and to the data position and back. i.e. converts a screen value to a corresponding data value or viewport value and vice versa.
I also have the tooltip opening event on this framework element and the pixel size of each of these markers. As long as the user hovers on a particular marker, I need to identify which point he has hovered on and let the markerpointsgraph set the tooltip value.
However, the transforms and methods for converting the values don't seem to be working fine, especially in the x direction. THe y - direction seems to be fine.
Below is some sample code that will explain the idea:
double selectedPointX = 0;
double selectedPointY = 0;
CoordinateTransform transformLocal = this.primaryPlotter.Transform;
if (series.SeriesDescription.YAxisAffinity == AxisAffinity_Y.Y1)
{
selectedPointX = Mouse.GetPosition(this.primaryPlotter).ScreenToViewport(transformLocal).X; //Getting the mouse positions
selectedPointY = Mouse.GetPosition(this.primaryPlotter).ScreenToViewport(transformLocal).Y;
}
else if (series.SeriesDescription.YAxisAffinity == AxisAffinity_Y.Y2 && injSecondaryAxis != null)
{
transformLocal = injSecondaryAxis.Transform;
selectedPointX = Mouse.GetPosition(this.injSecondaryAxis).ScreenToViewport(transformLocal).X;
selectedPointY = Mouse.GetPosition(this.injSecondaryAxis).ScreenToViewport(transformLocal).Y;
}
else if (series.SeriesDescription.YAxisAffinity == AxisAffinity_Y.Y3 && injTertiaryAxis != null)
{
transformLocal = injTertiaryAxis.Transform;
selectedPointX = Mouse.GetPosition(this.injTertiaryAxis).ScreenToViewport(transformLocal).X;
selectedPointY = Mouse.GetPosition(this.injTertiaryAxis).ScreenToViewport(transformLocal).Y;
}
foreach (var item in SeriesList)
{
if (item.Key == GraphKey)
{
for (int i = 0; i < item.Value.Collection.Count; i++)
{
//Calculate the size of the marker on the screen and allow for some level of inaccuracy in identifying the marker i.e anywhere within the marker is allowed.
double xlowerBound = item.Value.Collection[i].DataToViewport(transformLocal).X - series.MarkerSize;
double xUpperBound = item.Value.Collection[i].DataToViewport(transformLocal).X + series.MarkerSize;
double ylowerBound = item.Value.Collection[i].DataToViewport(transformLocal).Y - series.MarkerSize;
double yUpperBound = item.Value.Collection[i].DataToViewport(transformLocal).Y + series.MarkerSize;
//If point is within bounds
if (!(selectedPointX < xlowerBound || selectedPointX > xUpperBound || selectedPointY < ylowerBound || selectedPointY > yUpperBound))
{
strToolTip = item.Value.Collection[i].X + ", " + item.Value.Collection[i].Y; //This point is set as the tooltip
break;
}
}
break;
}
}
Here, injSecondary and injTertiary are two injected plotters that provide two vertical Y axes. They behave pretty similar to the primary chart plotter.
Is there anything wrong occurring here? For some reason, points well ahead of the actual clicked point are passing the buffer clause.
Hmm, looks like you're going about this the wrong way to me. If you dig into the source code of D3, you can open one of the marker classes and directly edit the tooltip there. Every System.Windows.Shapes element has a Tooltip property which you can assign right in the marker class. All you need to do is decide what data the tooltip holds.
Example - The CircleElementPointMarker Class :
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
namespace Microsoft.Research.DynamicDataDisplay.PointMarkers
{
/// <summary>Adds Circle element at every point of graph</summary>
public class CircleElementPointMarker : ShapeElementPointMarker {
public override UIElement CreateMarker()
{
Ellipse result = new Ellipse();
result.Width = Size;
result.Height = Size;
result.Stroke = Brush;
result.Fill = Fill;
if (!String.IsNullOrEmpty(ToolTipText))
{
ToolTip tt = new ToolTip();
tt.Content = ToolTipText;
result.ToolTip = tt;
}
return result;
}
public override void SetMarkerProperties(UIElement marker)
{
Ellipse ellipse = (Ellipse)marker;
ellipse.Width = Size;
ellipse.Height = Size;
ellipse.Stroke = Brush;
ellipse.Fill = Fill;
if (!String.IsNullOrEmpty(ToolTipText))
{
ToolTip tt = new ToolTip();
tt.Content = ToolTipText;
ellipse.ToolTip = tt;
}
}
public override void SetPosition(UIElement marker, Point screenPoint)
{
Canvas.SetLeft(marker, screenPoint.X - Size / 2);
Canvas.SetTop(marker, screenPoint.Y - Size / 2);
}
}
}
You can see in this class we have code built in to handle tooltips. The ToolTipText is a property that derives from the parent class - ShapeElementPointMarker. All you have to do is assign that property with the data that you need to show.
Use a cursorcoordinategraph on the plotter in place of the Mouse.GetPosition to get the correct screen position and avoid the ScreenToViewport transform. Also, use to DataToScreen while creating the bounds.