Silverlight: Missing DependencyObject.CoerceValue - wpf

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);
}
}
}

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);

How can I temporarily disable DependencyProperty constraints when changing DataContext

I've built a WPF UserControl which is a slider that permits selecting a range of values. It has four properties for the limits with the constraint
MinValue <= RangeMinValue <= RangeMaxValue <= MaxValue
On the basis of YAGNI I currently have MinValue forced to 0, and the other three use dependency properties with a PropertyChangedCallback to enforce the constraints:
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(nameof(MaxValue), typeof(int), typeof(RangeTrack),
new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, _EnforceConstraints));
public int MaxValue { get { return (int)GetValue(MaxValueProperty); } set { SetValue(MaxValueProperty, value); } }
public static readonly DependencyProperty RangeMinValueProperty = DependencyProperty.Register(nameof(RangeMinValue), typeof(int), typeof(RangeTrack),
new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, _EnforceConstraints));
public int RangeMinValue { get { return (int)GetValue(RangeMinValueProperty); } set { SetValue(RangeMinValueProperty, value); } }
public static readonly DependencyProperty RangeMaxValueProperty = DependencyProperty.Register(nameof(RangeMaxValue), typeof(int), typeof(RangeTrack),
new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, _EnforceConstraints));
public int RangeMaxValue { get { return (int)GetValue(RangeMaxValueProperty); } set { SetValue(RangeMaxValueProperty, value); } }
// Enforce the constraints MinValue <= RangeMinValue <= RangeMaxValue <= MaxValue
private static void _EnforceConstraints(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var track = (RangeTrack)d;
var newValue = (int)e.NewValue;
// We work on the basis that property changes will cascade, so if the constraints
// are violated we make one change which will cascade to tidy up the rest.
if (e.Property == MaxValueProperty)
{
// This should be the only test which doesn't involve adjacent values.
if (newValue < track.MinValue) { track.MaxValue = track.MinValue; return; }
if (newValue < track.RangeMaxValue) { track.RangeMaxValue = newValue; return; }
}
if (e.Property == RangeMaxValueProperty)
{
if (newValue > track.MaxValue) { track.RangeMaxValue = track.MaxValue; return; }
if (newValue < track.RangeMinValue) { track.RangeMinValue = newValue; return; }
}
if (e.Property == RangeMinValueProperty)
{
if (newValue < track.MinValue) { track.RangeMinValue = track.MinValue; return; }
if (newValue > track.RangeMaxValue) { track.RangeMaxValue = newValue; return; }
}
}
The control works perfectly in isolation, but there's a problem with "real life" usage:
<my:RangeSlider MaxValue="{Binding NumFoos}"
RangeMinValue="{Binding StartFoo}"
RangeMaxValue="{Binding EndFoo}" />
When the (inherited) DataContext changes, the bindings update, but not simultaneously. Result: the _EnforceConstraints callback will be called at a point where some of the values are obtained from binding against the old DataContext and some from the new DataContext, resulting in undesired changes.
Both DataContextProperty.OverrideMetadata and the FrameworkElement.DataContextChanged event fire before the bindings update, so in principle I could disable the constraints, but I'm not sure on what condition I could safely re-enable them.
Rejected attempts at a workaround
I'm willing to assume that the initial bound values of the DataContexts used will respect the constraints, and if a context is bound which doesn't then I don't mind the constraints changing its values to be consistent with itself. But even with those assumptions, I haven't found a satisfactory workaround.
I think the order of the properties in the XAML affects the order in which they're updated. But whatever order I put them in, there's a potential problem. If MaxValue updates first then the constraints can change RangeMaxValue on the old DataContext; if RangeMaxValue updates first then the constraints can clip it to the old context's MaxValue, changing the new DataContext.
I could remove the constraints entirely from the control and enforce them in the DataContext. This works, but in my opinion the logically correct place for the constraints is in the control.
I could remove the direct enforcement from the callback and instead apply the constraints in ArrangeOverride (since all of the DependencyProperties have FrameworkPropertyMetadataOptions.AffectsArrange). The control isn't rearranged until all of the properties have changed, so in principle this should work (although it means tracking some extra state to see whether the violation of RangeMinValue <= RangeMaxValue is because the min value was increased or the max value was decreased), but it feels very hacky.
Use CoerceValueCallbacks in conjunction with PropertyChangedCallbacks to trigger manual re-coercion.
private static void _MaxValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs evt)
{
// Cascade: MaxValue directly impacts on RangeMaxValue, which directly impacts on RangeMinValue, so they must be coerced in that order.
d.CoerceValue(RangeMaxValueProperty);
d.CoerceValue(RangeMinValueProperty);
}
private static object _CoerceMaxValue(DependencyObject d, object baseValue)
{
var track = (RangeTrack)d;
ValueType value = (ValueType)baseValue;
if (value < track.MinValue) value = track.MinValue;
return value;
}
private static void _RangeMaxValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs evt)
{
d.CoerceValue(RangeMinValueProperty);
}
private static object _CoerceRangeMaxValue(DependencyObject d, object baseValue)
{
var track = (RangeTrack)d;
ValueType value = (ValueType)baseValue;
// NB We might have track.RangeMinValue > track.MaxValue at certain points in the coercion cascade.
if (value < track.RangeMinValue) value = track.RangeMinValue;
if (value > track.MaxValue) value = track.MaxValue;
return value;
}
private static void _RangeMinValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs evt)
{
d.CoerceValue(RangeMaxValueProperty);
}
private static object _CoerceRangeMinValue(DependencyObject d, object baseValue)
{
var track = (RangeTrack)d;
ValueType value = (ValueType)baseValue;
if (value > track.RangeMaxValue) value = track.RangeMaxValue;
if (value < track.MinValue) value = track.MinValue;
return value;
}
The problem here is that if I try to "push" the thumb for RangeMaxValue by dragging the thumb for RangeMinValue, neither moves, but if I then move the RangeMaxValue thumb then the RangeMinValue jumps to catch up with it.
Is workaround 3 the best option going, or is there something I've missed?

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;
}

Changing the axes on WPF toolkit charting graph throws an exception

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!

How to set 'RowLength' for a textbox in Silverlight?

is it possible to set number of characters per row for a multiline (AcceptsReturn="True") textbox in Silverlight? Like Cols attribute of Textarea in HTML.
Not really. Normally you just set the height and width to whatever you want. Is there a particular reason why you want a certain number of characters on each line?
[EDIT]
I found some code here that splits a string into equal chunks:
Splitting a string into chunks of a certain size
Using that, I came up with the following. It works ok but needs some tweaking.
private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
var text = (sender as TextBox).Text.Replace(Environment.NewLine, "").Chunk(8).ToList();
(sender as TextBox).Text = String.Join(Environment.NewLine, text.ToArray());
(sender as TextBox).SelectionStart = (sender as TextBox).Text.Length;
}
And the extension method:
public static class Extensions
{
public static IEnumerable<string> Chunk(this string str, int chunkSize)
{
for (int i = 0; i < str.Length; i += chunkSize)
yield return str.Substring(i, Math.Min(chunkSize, str.Length - i));
}
}

Resources