Changing the axes on WPF toolkit charting graph throws an exception - wpf

I've a WPF toolkit chart which I want to be able to both specify the axis range and change the data being shown. Either of these work fine independently, but when I put them together I get an exception thrown from the binding code because the axis minimum and maximum are briefly reversed.
Here's some code:
<Common:MultiSeriesChart SeriesSource="{Binding Data}" >
<chartingToolkit:Chart.Axes>
<chartingToolkit:LinearAxis
Orientation="X" Minimum="{Binding MinimumMass}" Maximum="{Binding MaximumMass}"/>
<chartingToolkit:LinearAxis
Orientation="Y" Minimum="0" Maximum="100"/>
</chartingToolkit:Chart.Axes>
</Common:MultiSeriesChart>
And the code in the ViewModel (fairly standard MVVM stuff):
private double maximumMass;
public double MaximumMass
{
get { return maximumMass; }
set
{
if (maximumMass != value)
{
maximumMass = value;
RaisePropertyChanged(() => MaximumMass);
}
}
}
private double minimumMass;
public double MinimumMass
{
get { return minimumMass; }
set
{
if (minimumMass != value)
{
minimumMass = value;
RaisePropertyChanged(() => MinimumMass);
}
}
}
When I change the data being drawn I need to update the MinimumMass and MaximumMass for the X axis. First I update the MinimumMass to the new value, then the MaximumMass. This is fine unless the new MinimumMass value is bigger than the old MaximumMass, at which point the binding code in the graph throws an InvalidOperationException: The minimum value must be smaller than or equal to the maximum value.
I could update them in the opposite order, but then I'd get the same problem in reverse. Does anyone know the correct way to deal with this without a horrible kludge to ensure the maximum is temporarily too big to be beaten (i.e. set maximum to double.MaxValue, then set minimum to new minimum, then maximum to new maximum)?

In the end I just checked the order before assigning the new value:
if (newMin > MaximumMass)
{
MaximumMass = newMax;
MinimumMass = newMin;
}
else
{
MinimumMass = newMin;
MaximumMass = newMax;
}

Use try and catch
try
{
min = a;
max = b;
}
catch(exception)
{
// reassign values here for min, max
min = a;
max = b;
}
Good Luck!

Related

How to debug layout performance problems in WPF?

I'm writing a custom control for WPF (a drawn one) and I'm having massive performance issues. The fact is that I'm drawing a lots of text and this might be a part of the problem. I timed the OnRender method however, and I'm faced with very weird results - the whole method (especially after moving to GlyphRun implemenation) takes around 2-3ms to complete. Everything looks like following (take a look at the Output window for debug timing results) (requires Flash to play):
https://www.screencast.com/t/5p6mC6rxFv0
The OnRender method doesn't have anything special in particular, it just renders some rectangles and text:
protected override void OnRender(DrawingContext drawingContext)
{
var stopwatch = Stopwatch.StartNew();
ValidateMetrics();
base.OnRender(drawingContext);
var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
// Draw header
DrawHeader(drawingContext, pixelsPerDip);
// Draw margin
DrawMargin(drawingContext, pixelsPerDip);
// Draw data
DrawData(drawingContext, pixelsPerDip);
// Draw footer
DrawFooter(drawingContext);
stopwatch.Stop();
System.Diagnostics.Debug.WriteLine($"Drawing took {stopwatch.ElapsedMilliseconds}ms");
}
I ran Visual Studio's performance analysis and got the following results:
Clearly "Layout" of the editor control takes a lot of time, but still rendering is very quick.
How to debug this performance issue further? Why performance is so low despite OnRender taking milliseconds to run?
Edit - as response to comments
I didn't find a good way to time drawing, though I found, what was the cause of the problem and it turned out to be text drawing. I ended up with using very low-level text drawing mechanism, using GlyphRuns and it turned out to be fast enough to display full HD worth of text at least 30 frames per second, what was enough for me.
I'll post some relevant pieces of code below. Please note though: this is not ready-to-use solution, but should point anyone interested in the right direction.
private class GlyphRunInfo
{
public GlyphRunInfo()
{
CurrentPosition = 0;
}
public void FillMissingAdvanceWidths()
{
while (AdvanceWidths.Count < GlyphIndexes.Count)
AdvanceWidths.Add(0);
}
public List<ushort> GlyphIndexes { get; } = new List<ushort>();
public List<double> AdvanceWidths { get; } = new List<double>();
public double CurrentPosition { get; set; }
public double? StartPosition { get; set; }
}
// (...)
private void BuildTypeface()
{
typeface = new Typeface(FontFamily);
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
{
typeface = null;
glyphTypeface = null;
}
}
private void AddGlyph(char c, double position, GlyphRunInfo info)
{
if (glyphTypeface.CharacterToGlyphMap.TryGetValue(c, out ushort glyphIndex))
{
info.GlyphIndexes.Add(glyphIndex);
if (info.GlyphIndexes.Count > 1)
info.AdvanceWidths.Add(position - info.CurrentPosition);
info.CurrentPosition = position;
if (info.StartPosition == null)
info.StartPosition = info.CurrentPosition;
}
}
private void DrawGlyphRun(DrawingContext drawingContext, GlyphRunInfo regularRun, Brush brush, double y, double pixelsPerDip)
{
if (regularRun.StartPosition != null)
{
var glyphRun = new GlyphRun(glyphTypeface,
bidiLevel: 0,
isSideways: false,
renderingEmSize: FontSize,
pixelsPerDip: (float)pixelsPerDip,
glyphIndices: regularRun.GlyphIndexes,
baselineOrigin: new Point(Math.Round(regularRun.StartPosition.Value),
Math.Round(glyphTypeface.Baseline * FontSize + y)),
advanceWidths: regularRun.AdvanceWidths,
glyphOffsets: null,
characters: null,
deviceFontName: null,
clusterMap: null,
caretStops: null,
language: null);
drawingContext.DrawGlyphRun(brush, glyphRun);
}
}
And then some random code, which used the prior methods:
var regularRun = new GlyphRunInfo();
var selectionRun = new GlyphRunInfo();
// (...)
for (int ch = 0, index = line * Document.BytesPerRow; ch < charPositions.Count && index < availableBytes; ch++, index++)
{
byte drawnByte = dataBuffer[index];
char drawnChar = (drawnByte < 32 || drawnByte > 126) ? '.' : (char)drawnByte;
if (enteringMode == EnteringMode.Overwrite && (selection?.IsCharSelected(index + offset) ?? false))
AddGlyph(drawnChar, charPositions[ch].Position.TextCharX, selectionRun);
else
AddGlyph(drawnChar, charPositions[ch].Position.TextCharX, regularRun);
}
regularRun.FillMissingAdvanceWidths();
selectionRun.FillMissingAdvanceWidths();
DrawGlyphRun(drawingContext, regularRun, SystemColors.WindowTextBrush, linePositions[line].TextStartY, pixelsPerDip);
DrawGlyphRun(drawingContext, selectionRun, SystemColors.HighlightTextBrush, linePositions[line].TextStartY, pixelsPerDip);

WPF DataGridTextColumn Can't type point for float data

I had a WPF DataGrid and use DataGridTextColumn Binding to a Collection. The items in Collection had some float property.
When my program launched, I modify the value of float property in DataGrid, if I type a integer value, it works well. But if I type char . for a float value, char . can't be typed. I had to type all the numbers first, and then jump to the . position to type char . to finish my input.
So how can I type . in my situation?
Thanks.
Also met the same problem.
For my case, it is due to the data binding options.
I changed from *.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; to *.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;.
Then it can type float number directly.
It could be an issue with Localization.
Try changing the Culture settings of your thread to check out if this could be the problem:
using System.Globalization;
using System.Threading;
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en");
you can double-check the settings if you are unsure in which Culture you are running by going to Control Panel > Clock, Language and Region or running the following code:
using System.Diagnostics;
Debug.WriteLine("decimal: " + Thread.CurrentThread.CurrentUICulture.NumberFormat.NumberDecimalSeparator);
Debug.WriteLine("thousand: " + Thread.CurrentThread.CurrentUICulture.NumberFormat.NumberGroupSeparator);
Try this Regular Expression Validation in Binding.
<Validator:RegexValidationRule x:Key="DecimalValidatorFor3Digits"
RegularExpression="^\d{0,3}(\.\d{0,2})?$"
ErrorMessage="The field must contain only numbers with max 3 integers and 2 decimals" />
Thanks
Ck Nitin (TinTin)
I suppose it's because your datagridcolumn is bound to your class member with decimal datatype, e.g.
public class Product : ModelBase
{
decimal _price = 0;
public decimal Price
{
get { return _price; }
set { _price = value; OnPropertyChanged("Price"); }
}
}
and the UpdateSourceTrigger=PropertyChanged. One way to do get rid of it is to change the property to string type, and manipulate the string like below:
string _price = "0.00";
public string Price
{
get { return _price; }
set
{
string s = value;
decimal d = 0;
if (decimal.TryParse(value, out d))
_price = s;
else
_price = s.Substring(0, s.Length == 0 ? 0 : s.Length - 1);
OnPropertyChanged("Price");
}
}
Hope it helps

How to move by code the BindingSource to a specific record

Using datagridview bound to BindingSource control bound to a LINQ to SQL class, I wonder how to position the bindingSource to a specific record, that is, when I type a Product name in a textbox, the bindingsource should move to that specific product. Here is my code:
In my form FrmFind:
NorthwindDataContext dc;
private void FrmFind_Load(object sender, EventArgs e)
{
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list.OrderBy(o => o.ProductName);
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
int index = productBindingSource.Find("ProductName", tb.Text);
if (index >= 0)
{
productBindingSource.Position = index;
}
}
In the program class:
public class FindAbleBindingList<T> : BindingList<T>
{
public FindAbleBindingList()
: base()
{
}
public FindAbleBindingList(List<T> list)
: base(list)
{
}
protected override int FindCore(PropertyDescriptor property, object key)
{
for (int i = 0; i < Count; i++)
{
T item = this[i];
//if (property.GetValue(item).Equals(key))
if (property.GetValue(item).ToString().StartsWith(key.ToString()))
{
return i;
}
}
return -1; // Not found
}
}
How can I implement the find method to make it work?
You can combine the BindingSource.Find() method with the Position property.
For example, if you have something like this in your TextBox changed event handler:
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
int index = bs.Find("Product", tb.Text);
if (index >= 0)
{
bs.Position = index;
}
}
This of course will depend on a lot of things like the particular implementation of the Find method the data source for the binding source has.
In a question you asked a little while ago I gave you an implementation for Find which worked with full matches. Below is a slightly different implementation that will look at the start of the property being inspected:
protected override int FindCore(PropertyDescriptor property, object key)
{
// Simple iteration:
for (int i = 0; i < Count; i++)
{
T item = this[i];
if (property.GetValue(item).ToString().StartsWith(key.ToString()))
{
return i;
}
}
return -1; // Not found
}
Do note that the above method is case sensitive - you can change StartsWith to be case insensitive if you need.
One key thing to note about the way .Net works is that the actual type of an object is not sufficient all the time - the declared type is what consuming code knows about.
This is the reason why you get a NotSupported exception when calling the Find method, even though your BindingList implementation has a Find method - the code that receives this binding list doesn't know about the Find.
The reason for that is in these lines of code:
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list.OrderBy(o => o.ProductName);
When you set the data source for the binding source you include the extension method OrderBy - Checking this shows that it returns IOrderedEnumerable, an interface described here on MSDN. Note that this interface has no Find method, so even though the underlying FindableBindingList<T> supports Find the binding source doesn't know about it.
There are several solutions (the best is in my opinion to extend your FindableBindingList to also support sorting and sort the list) but the quickest for your current code is to sort earlier like so:
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).OrderBy(p => p.ProductName).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list;
In WinForms there are no entirely out of the box solutions for the things you are trying to do - they all need a little bit of custom code that you need to put together to match just your own requirements.
I took a different approach. I figured, programmatically, every record must be checked until a match is found, so I just iterated using the MoveNext method until I found a match. Unsure if the starting position would be the First record or not, so I used the MoveFirst method to ensure that is was.
There is one assumption, and that is that what you are searching for is unique in that column. In my case, I was looking to match an Identity integer.
int seekID;
this.EntityTableBindingSource.MoveFirst();
if (seekID > 0)
{
foreach (EntityTable sd in EntityTableBindingSource)
{
if (sd.ID != seekID)
{
this.t_EntityTableBindingSource.MoveNext();
}
else
{
break;
}
}
}
I didn't really care for either answer provided. Here is what I came up with for my problem:
// Create a list of items in the BindingSource and use labda to find your row:
var QuickAccessCode = customerListBindingSource.List.OfType<CustomerList>()
.ToList().Find(f => f.QuickAccessCode == txtQAC.Text);
// Then use indexOf to find the object in your bindingSource:
var pos = customerListBindingSource.IndexOf(QuickAccessCode);
if (pos < 0)
{
MessageBox.Show("Could not find " + txtQAC.Text);
}
else
{
mainFrm.customerListBindingSource.Position = pos;
}

Is there a more efficient way to detect polygon overlap/intersection than PathGeometry.FillContainsWithDetail()?

I have a method that is gobbling up 25% of my cpu time. I call this method about 27,000 times per second. (Yup, lots of calls since it's updating frequently). I am wondering if anybody knows a faster way to detect if 2 polygons overlap. Basically, I have to check the moving objects on the screen against stationary objects on the screen. I am using PathGeometry and the two calls below are using up 25% of the cpu time used by my program. The PointCollection objects I am passing just contain 4 points representing 4 corners of a polygon. They may not create a rectangular area, but all the points are connected. I guess a trapazoid would be the shape.
These methods are short and were very easy to implement, but I think I might want to opt for a more complicated solution if I can have it run more quickly than the code below. Any ideas?
public static bool PointCollectionsOverlap(PointCollection area1, PointCollection area2)
{
PathGeometry pathGeometry1 = GetPathGeometry(area1);
PathGeometry pathGeometry2 = GetPathGeometry(area2);
return pathGeometry1.FillContainsWithDetail(pathGeometry2) != IntersectionDetail.Empty;
}
public static PathGeometry GetPathGeometry(PointCollection polygonCorners)
{
List<PathSegment> pathSegments = new List<PathSegment>
{ new PolyLineSegment(polygonCorners, true) };
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(new PathFigure(polygonCorners[0], pathSegments, true));
return pathGeometry;
}
Ok, after lots of research and finding many partial answers, but none that fully answered the question, I have found a faster way and it is actually about 4.6 times faster than the old way.
I created a special test app to test the speed this. You can find the test app here. If you download it, you can see a checkbox at the top of the app. Check and uncheck it to switch back and forth between the old way and the new way. The app generates a bunch of random polygons and the borders of the polygons change to white when they intersect another polygon. The numbers to the left of the 'Redraw' button are to allow you to enter the Number of Polygons, Max Length of a side, and Max offset from square (to make them less square and more odd shaped). Push 'Refresh' to clear and regenerate new polygons with the settings you've entered.
Anyway, here is the code for the two different implementations. You pass in a collection of the points that make up each polygon. The old way uses less code, but is 4.6 times slower than the new way.
Oh, one quick note. The new way has a couple calls to 'PointIsInsidePolygon'. These were necessary because without it, the method returned false when one polygon was entirely contained within a different polygon. But the PointIsInsidePolygon method fixes that problem.
Hope this all helps somebody else out with polygon intercepts and overlaps.
Old Way (4.6 times slower. YES REALLY 4.6 TIMES slower):
public static bool PointCollectionsOverlap_Slow(PointCollection area1, PointCollection area2)
{
PathGeometry pathGeometry1 = GetPathGeometry(area1);
PathGeometry pathGeometry2 = GetPathGeometry(area2);
bool result = pathGeometry1.FillContainsWithDetail(pathGeometry2) != IntersectionDetail.Empty;
return result;
}
public static PathGeometry GetPathGeometry(PointCollection polygonCorners)
{
List<PathSegment> pathSegments = new List<PathSegment> { new PolyLineSegment(polygonCorners, true) };
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(new PathFigure(polygonCorners[0], pathSegments, true));
return pathGeometry;
}
New Way (4.6 times faster. YES REALLY 4.6 TIMES faster):
public static bool PointCollectionsOverlap_Fast(PointCollection area1, PointCollection area2)
{
for (int i = 0; i < area1.Count; i++)
{
for (int j = 0; j < area2.Count; j++)
{
if (lineSegmentsIntersect(area1[i], area1[(i + 1) % area1.Count], area2[j], area2[(j + 1) % area2.Count]))
{
return true;
}
}
}
if (PointCollectionContainsPoint(area1, area2[0]) ||
PointCollectionContainsPoint(area2, area1[0]))
{
return true;
}
return false;
}
public static bool PointCollectionContainsPoint(PointCollection area, Point point)
{
Point start = new Point(-100, -100);
int intersections = 0;
for (int i = 0; i < area.Count; i++)
{
if (lineSegmentsIntersect(area[i], area[(i + 1) % area.Count], start, point))
{
intersections++;
}
}
return (intersections % 2) == 1;
}
private static double determinant(Vector vector1, Vector vector2)
{
return vector1.X * vector2.Y - vector1.Y * vector2.X;
}
private static bool lineSegmentsIntersect(Point _segment1_Start, Point _segment1_End, Point _segment2_Start, Point _segment2_End)
{
double det = determinant(_segment1_End - _segment1_Start, _segment2_Start - _segment2_End);
double t = determinant(_segment2_Start - _segment1_Start, _segment2_Start - _segment2_End) / det;
double u = determinant(_segment1_End - _segment1_Start, _segment2_Start - _segment1_Start) / det;
return (t >= 0) && (u >= 0) && (t <= 1) && (u <= 1);
}

Silverlight: Missing DependencyObject.CoerceValue

In Silverlight there is no DependencyObject.CoerceValue. I am looking for an alternative, to do the following WPF-Code also in Silverlight.
The situation:
There is a class Range, which has several DependencyProperties: MinimumProperty, MaximumProperty, LowerValueProperty and UpperValueProperty.
Minimum may never be greater then Maximum, Maximum never be smaller than Minimum. Moreover LowerValue and UpperValue have to be in between Minimum and Maximum, whereas LowerValue always smaller then UpperValue.
All DependencyProperties are implemented like this (in WPF):
public static new readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum",
typeof(double),
typeof(Range),
new FrameworkPropertyMetadata(0d,
new PropertyChangedCallback(Range.OnMinimumChanged),
new CoerceValueCallback(Range.CoerceMinimum)),
new ValidateValueCallback(Range.ValidateMinimum));
public new double Minimum
{
get { return (double)base.GetValue(MinimumProperty); }
set { base.SetValue(MinimumProperty, value); }
}
The coercion in WPF is done like that:
private static object CoerceMinimum(DependencyObject source, object value)
{
Range r = source as Range;
double maximum = r.Maximum;
double val = (double)value;
if (val > maximum)
{
return maximum;
}
return value;
}
PropertyChangedCallback looks like this:
private static void OnMinimumChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Range r = source as Range;
r.CoerceValue(LowerValueProperty);
r.CoerceValue(UpperValueProperty);
r.CoerceValue(MaximumProperty);
}
The ValidateValueCallback doesn't matter in this case. The other Callbacks are similar to the shown code.
In WPF this runs good. For example I set (in XAML)
<Range LowerValue="12" Minimum="10" UpperValue="15" Maximum="20" />
all values are correct. The order doesn't matter!
But in Silverlight I do not get it running.
First step is the workaround for CoerceValueCallback. I raise the coercion in PropertyChangedCallback, like this:
private static void OnMinimumChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Range r = source as Range;
double newVal = (double)e.NewValue;
double coercedVal = (double)CoerceMinimum(source, newVal);
if (coercedVal != newVal)
{
r.Minimum = coercecVal;
return;
}
r.CoerceValue(LowerValueProperty);
r.CoerceValue(UpperValueProperty);
r.CoerceValue(MaximumProperty);
}
If now Minimum is set to an value, the CoerceMinimum is still executed and the Minimum-Coercion done well.
But the last three lines do not compile, because DependencyObject has no CoerceValue-Method. And exactly this is the position where I am at one's wits' end.
How do I raise the Coercion for LowerValue, UpperValue and Maximum on MinimumChanged?
Or is there another way to ensure, that the order of initialization does not matter and all properties are set correctly (assuming that the condition are fulfilled)?
Thanks in advance!
I got a solution :) I am sure, it isnt completed yet. I've still to debug all possible orders, but I think most already done.
I post just the code for LowerValue, the others are nearly the same:
OnUpperValueChanged raise CoerceUpperValueChanged and
CoerceLowerValueChanged.
OnMinimumChanged raise CoerceMinimum,
CoerceUpperValueChanged and
CoerceLowerValueChanged.
OnMaximumChanged raise CoerceMaximum, CoerceUpperValueChanged and CoerceLowerValueChanged.
The coercion of Minimum and Maximum just easy. If new value is greater than Maximum (or smaller than Minimum) take Maximum (or Minimum), else take the new value.
Here's the code:
private static void OnLowerValueChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Range r = source as Range;
if (r._levelsFromRootCall == 0)
{
r._requestedLowerVal = (double)e.NewValue;
}
r._levelsFromRootCall++;
r.CoerceLowerValue();
r.CoerceUpperValue();
r._levelsFromRootCall--;
}
private void CoerceLowerValue()
{
double minimum = this.Minimum;
double maximum = this.Maximum;
double lowerVal = this.LowerValue;
double upperVal = this.UpperValue;
if (lowerVal != _requestedLowerVal && _requestedLowerVal >= minimum && _requestedLowerVal <= maximum)
{
if (_requestedLowerVal <= upperVal)
{
base.SetValue(LowerValueProperty, _requestedLowerVal);
}
else
{
base.SetValue(LowerValueProperty, upperVal);
}
}
else
{
if (lowerVal < minimum)
{
base.SetValue(LowerValueProperty, minimum);
}
else if (lowerVal > upperVal || _requestedLowerVal > upperVal)
{
base.SetValue(LowerValueProperty, upperVal);
}
else if (lowerVal > maximum)
{
base.SetValue(LowerValueProperty, maximum);
}
}
}

Resources