Using Silverlight DispatcherTimer - is there a better way (DependencyProperty on Animation)? - silverlight

I'm animating a 'race' on a map. The race takes 45 minutes, but the animation runs for 60 seconds.
You can watch the 2008 City2Surf race demo to see what I mean.
The 'race clock' in the top-left must show "real time", and had to be set-up in the .xaml.cs with a System.Windows.Threading.DispatcherTimer which seems a bit of a hack.
I thought maybe there'd be a DependencyProperty on the animation rather than just StoryBoard.GetCurrentTime(), but instead I have had to
// SET UP AND START TIMER, before StoryBoard.Begin()
dt = new System.Windows.Threading.DispatcherTimer();
dt.Interval = new TimeSpan(0, 0, 0, 0, 100); // 0.1 second
dt.Tick +=new EventHandler(dt_Tick);
winTimeRatio = (realWinTime.TotalSeconds * 1.0) / animWinTime.TotalSeconds;
dt.Start();
and then the Tick event handler
void dt_Tick(object sender, EventArgs e)
{
var sb = LayoutRoot.Resources["Timeline"] as Storyboard;
TimeSpan ts = sb.GetCurrentTime();
TimeSpan toDisplay = new TimeSpan(0,0,
Convert.ToInt32(ts.TotalSeconds * winTimeRatio));
RaceTimeText.Text = toDisplay.ToString();
}
This works and seems to perform OK - but my question is: am I missing something in the Silverlight animation/storyboard classes that would do this more neatly? I have to remember to stop the DispatcherTimer too!
Or to put the question another way: any better suggestions on 'animation' of TextBox content (the .Text itself, not the location/dimensions/etc)?

That is one way. It's nice and simple, but a bit messy. You could get rid of the storyboard and on each tick, increment a local value by the tick interval and use that to set your time. You would then only have one time piece.
Or... A more elegant and re-usable way would be to create a helper class that is a DependencyObject. I would also just use a StoryBoard with a DoubleAnimation an bind the Storyboard.Target to an instance of the DoubleTextblockSetter. Set the storyboard Duration to your time and set the value to your time in seconds. Here is the DoublerBlockSetterCode.
public class DoubleTextBlockSetter : DependencyObject
{
private TextBlock textBlock { get; private set; }
private IValueConverter converter { get; private set; }
private object converterParameter { get; private set; }
public DoubleTextBlockSetter(
TextBlock textBlock,
IValueConverter converter,
object converterParameter)
{
this.textBlock = textBlock;
this.converter = converter;
this.converterParameter = converterParameter;
}
#region Value
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(double),
typeof(DoubleTextBlockSetter),
new PropertyMetadata(
new PropertyChangedCallback(
DoubleTextBlockSetter.ValuePropertyChanged
)
)
);
private static void ValuePropertyChanged(
DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
DoubleTextBlockSetter control = obj as DoubleTextBlockSetter;
if (control != null)
{
control.OnValuePropertyChanged();
}
}
public double Value
{
get { return (double)this.GetValue(DoubleTextBlockSetter.ValueProperty); }
set { base.SetValue(DoubleTextBlockSetter.ValueProperty, value); }
}
protected virtual void OnValuePropertyChanged()
{
this.textBlock.Text = this.converter.Convert(
this.Value,
typeof(string),
this.converterParameter,
CultureInfo.CurrentCulture) as string;
}
#endregion
}
Then you might have a format converter:
public class TicksFormatConverter : IValueConverter
{
TimeSpanFormatProvider formatProvider = new TimeSpanFormatProvider();
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
long numericValue = 0;
if (value is int)
{
numericValue = (long)(int)value;
}
else if (value is long)
{
numericValue = (long)value;
}
else if (value is double)
{
numericValue = (long)(double)value;
}
else
throw new ArgumentException("Expecting type of int, long, or double.");
string formatterString = null;
if (parameter != null)
{
formatterString = parameter.ToString();
}
else
{
formatterString = "{0:H:m:ss}";
}
TimeSpan timespan = new TimeSpan(numericValue);
return string.Format(this.formatProvider, formatterString, timespan);
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
I almost forgot the TimespanFormatProvider. There is no format provider for timespan in Silverlight, so it appears.
public class TimeSpanFormatProvider : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType != typeof(ICustomFormatter))
return null;
return this;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
string formattedString;
if (arg is TimeSpan)
{
TimeSpan ts = (TimeSpan)arg;
DateTime dt = DateTime.MinValue.Add(ts);
if (ts < TimeSpan.FromDays(1))
{
format = format.Replace("d.", "");
format = format.Replace("d", "");
}
if (ts < TimeSpan.FromHours(1))
{
format = format.Replace("H:", "");
format = format.Replace("H", "");
format = format.Replace("h:", "");
format = format.Replace("h", "");
}
// Uncomment of you want to minutes to disappear below 60 seconds.
//if (ts < TimeSpan.FromMinutes(1))
//{
// format = format.Replace("m:", "");
// format = format.Replace("m", "");
//}
if (string.IsNullOrEmpty(format))
{
formattedString = string.Empty;
}
else
{
formattedString = dt.ToString(format, formatProvider);
}
}
else
throw new ArgumentNullException();
return formattedString;
}
}
All that stuff is re-usable and should live in your tool box. I pulled it from mine. Then, of course, you wire it all together:
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
sb.Children.Add(da);
DoubleTextBlockSetter textBlockSetter = new DoubleTextBlockSetter(
Your_TextBlock,
new TicksFormatConverter(),
"{0:m:ss}"); // DateTime format
Storyboard.SetTarget(da, textBlockSetter);
da.From = Your_RefreshInterval_Secs * TimeSpan.TicksPerSecond;
da.Duration = new Duration(
new TimeSpan(
Your_RefreshInterval_Secs * TimeSpan.TicksPerSecond));
sb.begin();
And that should do the trick. An it's only like a million lines of code. And we haven't even written Hello World just yet...;) I didn't compile that, but I did Copy and Paste the 3 classes directly from my library. I've used them quite a lot. It works great. I also use those classes for other things. The TickFormatConverter comes in handy when data binding. I also have one that does Seconds. Very useful. The DoubleTextblockSetter allows me to animate numbers, which is really fun. Especially when you apply different types of interpolation.
Enjoy.

Related

How to set a dependency property from InLine event of a textblock?

I'm trying to set the dependency property "WordPad" from within the Inline event of MouseEnter from a CustomTextBlock. But this error results:
An object reference is required for the non-static field, method, or property 'WpfCustomControlLibrary.CustomTextBlock.WordPad.get'
How can I achieve this?
Any help would be most appreciated. Thanks.
Given the following class:
public class CustomTextBlock : TextBlock
{
public string InLineText
{
get { return (string)GetValue(InLineTextProperty); }
set { SetValue(InLineTextProperty, value); }
}
public static readonly DependencyProperty InLineTextProperty =
DependencyProperty.Register("InLineText", typeof(string), typeof(CustomTextBlock),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(o, e) =>
{
//PropertyChangedCallback
CustomTextBlock tb = (CustomTextBlock)o;
string text = (string)e.NewValue;
tb.Inlines.Clear();
if (String.IsNullOrEmpty(text))
return;
List<Inline> inlines = new List<Inline>();
string[] words = Regex.Split(text, #"(\s+)");
Inline inline = null;
foreach (string s in words)
{
Run run = new Run(s);
inline = run;
inline.MouseEnter += new System.Windows.Input.MouseEventHandler(inline_MouseEnter);
inline.MouseLeave += new System.Windows.Input.MouseEventHandler(inline_MouseLeave);
tb.Inlines.Add(inline);
}
}));
public WritingPad WordPad
{
get { return (WritingPad)GetValue(WordPadProperty); }
set { SetValue(WordPadProperty, value); }
}
public static readonly DependencyProperty WordPadProperty =
DependencyProperty.Register("WordPad", typeof(WritingPad), typeof(CustomTextBlock), new UIPropertyMetadata(null));
static void inline_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Run Sender = sender as Run;
TextPointer tp0 = Sender.ContentStart;
TextPointer tp1 = Sender.ContentEnd;
Rect StartRect = tp0.GetCharacterRect(LogicalDirection.Forward);
Rect EndRect = tp1.GetCharacterRect(LogicalDirection.Backward);
StartRect.Union(EndRect);
WordPad = new WritingPad(); <--**THIS FAILS ????
}
Make the inline_MouseEnter and inline_MouseLeave methods non-static and attach them to your Runs like this:
inline.MouseEnter += tb.inline_MouseEnter;
inline.MouseLeave += tb.inline_MouseLeave;
Even better would be to make the whole PropertyChangedCallback non-static and then write the dependency property declaration like this:
public static readonly DependencyProperty InLineTextProperty =
DependencyProperty.Register("InLineText", typeof(string), typeof(CustomTextBlock),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(o, e) => ((CustomTextBlock)o).InlineTextChanged((string)e.NewValue)));
private void InlineTextChanged(string text)
{
Inlines.Clear();
if (!string.IsNullOrEmpty(text))
{
foreach (string s in Regex.Split(text, #"(\s+)"))
{
var run = new Run(s);
run.MouseEnter += inline_MouseEnter;
run.MouseLeave += inline_MouseLeave;
Inlines.Add(run);
}
}
}
I'm hopeful a guru can give a better solution.
It should be noted that Run has a "Parent" property which resolved to this instance of CustomTextBlock. Hence, this seems to work,
CustomTextBlock ctb = Sender.Parent as CustomTextBlock;
ctb.WordPad = new WritingPad
{
WordBounds = StartRect,
WritingPadMode = Enumerations.WritingPadModes.EditingByWord
};

WPF - Can List<T> be used as a dependencyProperty

I have a simple search text box. This text box acts as a filter. I've now copied/pasted the code for the 5th time and enough is enough. Time for a custom control.
left and right brackets have been replaced with ()
My custom control will be simple. My problem is I want to have a dependencyProperty on this control that is of type List(T).
I created a test project to proof it out and make sure it works. It works well. Ignore List.
Below is the entire class. The problem is that the only thing holding me up is replacing List (Person) with List(T). Something like List where: T is Object
typeof(List(T) where: T is Object) <= Obviously I can't do that but gives an idea what I'm trying to accomplish.
public class SearchTextBox : TextBox
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("FilterSource", typeof(List<Person>), typeof(SearchTextBox), new UIPropertyMetadata(null)); //I WANT THIS TO BE LIST<T>
public List<Person> FilterSource
{
get
{
return (List<Person>)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
public static readonly DependencyProperty FilterPropertyNameProperty =
DependencyProperty.Register("FilterPropertyName", typeof(String), typeof(SearchTextBox), new UIPropertyMetadata());
public String FilterPropertyName
{
get
{
return (String)GetValue(FilterPropertyNameProperty);
}
set
{
SetValue(FilterPropertyNameProperty, value);
}
}
public SearchTextBox()
{
this.KeyUp += new System.Windows.Input.KeyEventHandler(SearchBox_KeyUp);
}
void SearchBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(FilterSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterTheSource);
}
bool FilterTheSource(object obj)
{
if (obj == null) return false;
Type t = obj.GetType();
PropertyInfo pi = t.GetProperty(FilterPropertyName);
//object o = obj.GetType().GetProperty(FilterPropertyName);
String propertyValue = obj.GetType().GetProperty(FilterPropertyName).GetValue(obj, null).ToString().ToLower();
if (propertyValue.Contains(this.Text.ToLower()))
{
return true;
}
return false;
}
}
No; that's not possible.
Instead, just use the non-generic typeof(IList).

GridLength animation using keyframes?

I want to know are there any classes that I can animate a GridLength value using KeyFrames? I have seen the following sites, but none of them were with KeyFrames:
http://windowsclient.net/learn/video.aspx?v=70654
http://marlongrech.wordpress.com/2007/08/20/gridlength-animation/
Any advice?
Create an attached behavior and animate it instead.
Sure, GridLength clearly is not a numeric type and as such it's not clear how it can be animated. To compnesate that I can create an attached behavior like:
public class AnimatableProperties
{
public static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached("Width",
typeof(double),
typeof(DependencyObject),
new PropertyMetadata(-1, (o, e) =>
{
AnimatableProperties.OnWidthChanged((Grid)o, (double)e.NewValue);
}));
public static void SetWidth(DependencyObject o,
double e)
{
o.SetValue(AnimatableProperties.WidthProperty, e);
}
public static double GetWidth(DependencyObject o)
{
return (double)o.GetValue(AnimatableProperties.WidthProperty);
}
private static void OnWidthChanged(DependencyObject target,
double e)
{
target.SetValue(Grid.WidthProperty, new GridLength(e));
}
}
That will re-inroduce Grid width as numeric property of double type. Having that in place you can freely animate it.
P.S. Obviously it doesn't make much sense to use Grid's Width as it's already double. any other GridLength based properties can be wrpapped with double wrappers as per the sample above and then animated via that wrappers.
It is fairly straight forward but you need to use an adapter because you can't directly animate Width on the ColumnDefinition class with a DoubleAnimator because ColumnDefinition is not a double. Here's my code:
public class ColumnDefinitionDoubleAnimationAdapter : Control
{
#region Dependency Properties
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(nameof(Width), typeof(double), typeof(ColumnDefinitionDoubleAnimationAdapter), new PropertyMetadata((double)0, WidthChanged));
private static void WidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var columnDefinitionDoubleAnimationAdapter = (ColumnDefinitionDoubleAnimationAdapter)d;
columnDefinitionDoubleAnimationAdapter.Width = (double)e.NewValue;
}
#endregion
#region Fields
private ColumnDefinition _ColumnDefinition;
#endregion
#region Constructor
public ColumnDefinitionDoubleAnimationAdapter(ColumnDefinition columnDefinition)
{
_ColumnDefinition = columnDefinition;
}
#endregion
#region Public Properties
public double Width
{
get
{
return (double)GetValue(WidthProperty);
}
set
{
SetValue(WidthProperty, value);
_ColumnDefinition.Width = new GridLength(value);
}
}
#endregion
}
Unfortunately the above is pretty inefficient because it creates a GridLength again and again because ColumnDefinition.Width.Value should be read only.
Here is a method to do the animation. It's important that it uses Task based async because otherwise the storyboard will go out of scope and cause bad behaviour. This is good practice anyway so you can await the animation if you need to:
public async static Task AnimateColumnWidth(ColumnDefinition columnDefinition, double from, double to, TimeSpan duration, IEasingFunction ease)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
var storyboard = new Storyboard();
var animation = new DoubleAnimation();
animation.EasingFunction = ease;
animation.Duration = new Duration(duration);
storyboard.Children.Add(animation);
animation.From = from;
animation.To = to;
var columnDefinitionDoubleAnimationAdapter = new ColumnDefinitionDoubleAnimationAdapter(columnDefinition);
Storyboard.SetTarget(animation, columnDefinitionDoubleAnimationAdapter);
Storyboard.SetTargetProperty(animation, new PropertyPath(ColumnDefinitionDoubleAnimationAdapter.WidthProperty));
storyboard.Completed += (a, b) =>
{
taskCompletionSource.SetResult(true);
};
storyboard.Begin();
await taskCompletionSource.Task;
}
And an example usage:
private async void TheMenu_HamburgerToggled(object sender, EventArgs e)
{
TheMenu.IsOpen = !TheMenu.IsOpen;
var twoSeconds = TimeSpan.FromMilliseconds(120);
var ease = new CircleEase { EasingMode = TheMenu.IsOpen ? EasingMode.EaseIn : EasingMode.EaseOut };
if (TheMenu.IsOpen)
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 40, 320, twoSeconds, ease);
}
else
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 320, 40, twoSeconds, ease);
}
}

Databind a read only dependency property to ViewModel in Xaml

I'm trying to databind a Button's IsMouseOver read-only dependency property to a boolean read/write property in my view model.
Basically I need the Button's IsMouseOver property value to be read to a view model's property.
<Button IsMouseOver="{Binding Path=IsMouseOverProperty, Mode=OneWayToSource}" />
I'm getting a compile error: 'IsMouseOver' property is read-only and cannot be set from markup. What am I doing wrong?
No mistake. This is a limitation of WPF - a read-only property cannot be bound OneWayToSource unless the source is also a DependencyProperty.
An alternative is an attached behavior.
As many people have mentioned, this is a bug in WPF and the best way is to do it is attached property like Tim/Kent suggested. Here is the attached property I use in my project. I intentionally do it this way for readability, unit testability, and sticking to MVVM without codebehind on the view to handle the events manually everywhere.
public interface IMouseOverListener
{
void SetIsMouseOver(bool value);
}
public static class ControlExtensions
{
public static readonly DependencyProperty MouseOverListenerProperty =
DependencyProperty.RegisterAttached("MouseOverListener", typeof (IMouseOverListener), typeof (ControlExtensions), new PropertyMetadata(OnMouseOverListenerChanged));
private static void OnMouseOverListenerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = ((UIElement)d);
if(e.OldValue != null)
{
element.MouseEnter -= ElementOnMouseEnter;
element.MouseLeave -= ElementOnMouseLeave;
}
if(e.NewValue != null)
{
element.MouseEnter += ElementOnMouseEnter;
element.MouseLeave += ElementOnMouseLeave;
}
}
public static void SetMouseOverListener(UIElement element, IMouseOverListener value)
{
element.SetValue(MouseOverListenerProperty, value);
}
public static IMouseOverListener GetMouseOverListener(UIElement element)
{
return (IMouseOverListener) element.GetValue(MouseOverListenerProperty);
}
private static void ElementOnMouseLeave(object sender, MouseEventArgs mouseEventArgs)
{
var element = ((UIElement)sender);
var listener = GetMouseOverListener(element);
if(listener != null)
listener.SetIsMouseOver(false);
}
private static void ElementOnMouseEnter(object sender, MouseEventArgs mouseEventArgs)
{
var element = ((UIElement)sender);
var listener = GetMouseOverListener(element);
if (listener != null)
listener.SetIsMouseOver(true);
}
}
Here's a rough draft of what i resorted to while seeking a general solution to this problem. It employs a css-style formatting to specify Dependency-Properties to be bound to model properties (models gotten from the DataContext); this also means it will work only on FrameworkElements.
I haven't thoroughly tested it, but the happy path works just fine for the few test cases i ran.
public class BindingInfo
{
internal string sourceString = null;
public DependencyProperty source { get; internal set; }
public string targetProperty { get; private set; }
public bool isResolved => source != null;
public BindingInfo(string source, string target)
{
this.sourceString = source;
this.targetProperty = target;
validate();
}
private void validate()
{
//verify that targetProperty is a valid c# property access path
if (!targetProperty.Split('.')
.All(p => Identifier.IsMatch(p)))
throw new Exception("Invalid target property - " + targetProperty);
//verify that sourceString is a [Class].[DependencyProperty] formatted string.
if (!sourceString.Split('.')
.All(p => Identifier.IsMatch(p)))
throw new Exception("Invalid source property - " + sourceString);
}
private static readonly Regex Identifier = new Regex(#"[_a-z][_\w]*$", RegexOptions.IgnoreCase);
}
[TypeConverter(typeof(BindingInfoConverter))]
public class BindingInfoGroup
{
private List<BindingInfo> _infoList = new List<BindingInfo>();
public IEnumerable<BindingInfo> InfoList
{
get { return _infoList.ToArray(); }
set
{
_infoList.Clear();
if (value != null) _infoList.AddRange(value);
}
}
}
public class BindingInfoConverter: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string)) return true;
return base.CanConvertFrom(context, sourceType);
}
// Override CanConvertTo to return true for Complex-to-String conversions.
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string)) return true;
return base.CanConvertTo(context, destinationType);
}
// Override ConvertFrom to convert from a string to an instance of Complex.
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string text = value as string;
return new BindingInfoGroup
{
InfoList = text.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(binfo =>
{
var parts = binfo.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2) throw new Exception("invalid binding info - " + binfo);
return new BindingInfo(parts[0].Trim(), parts[1].Trim());
})
};
}
// Override ConvertTo to convert from an instance of Complex to string.
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture,
object value, Type destinationType)
{
var bgroup = value as BindingInfoGroup;
return bgroup.InfoList
.Select(bi => $"{bi.sourceString}:{bi.targetProperty};")
.Aggregate((n, p) => n += $"{p} ")
.Trim();
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => false;
}
public class Bindings
{
#region Fields
private static ConcurrentDictionary<DependencyProperty, PropertyChangeHandler> _Properties =
new ConcurrentDictionary<DependencyProperty, PropertyChangeHandler>();
#endregion
#region OnewayBindings
public static readonly DependencyProperty OnewayBindingsProperty =
DependencyProperty.RegisterAttached("OnewayBindings", typeof(BindingInfoGroup), typeof(Bindings), new FrameworkPropertyMetadata
{
DefaultValue = null,
PropertyChangedCallback = (x, y) =>
{
var fwe = x as FrameworkElement;
if (fwe == null) return;
//resolve the bindings
resolve(fwe);
//add change delegates
(GetOnewayBindings(fwe)?.InfoList ?? new BindingInfo[0])
.Where(bi => bi.isResolved)
.ToList()
.ForEach(bi =>
{
var descriptor = DependencyPropertyDescriptor.FromProperty(bi.source, fwe.GetType());
PropertyChangeHandler listener = null;
if (_Properties.TryGetValue(bi.source, out listener))
{
descriptor.RemoveValueChanged(fwe, listener.callback); //cus there's no way to check if it had one before...
descriptor.AddValueChanged(fwe, listener.callback);
}
});
}
});
private static void resolve(FrameworkElement element)
{
var bgroup = GetOnewayBindings(element);
bgroup.InfoList
.ToList()
.ForEach(bg =>
{
//source
var sourceParts = bg.sourceString.Split('.');
if (sourceParts.Length == 1)
{
bg.source = element.GetType()
.baseTypes() //<- flattens base types, including current type
.SelectMany(t => t.GetRuntimeFields()
.Where(p => p.IsStatic)
.Where(p => p.FieldType == typeof(DependencyProperty)))
.Select(fi => fi.GetValue(null) as DependencyProperty)
.FirstOrDefault(dp => dp.Name == sourceParts[0])
.ThrowIfNull($"Dependency Property '{sourceParts[0]}' was not found");
}
else
{
//resolve the dependency property [ClassName].[PropertyName]Property - e.g FrameworkElement.DataContextProperty
bg.source = Type.GetType(sourceParts[0])
.GetField(sourceParts[1])
.GetValue(null)
.ThrowIfNull($"Dependency Property '{bg.sourceString}' was not found") as DependencyProperty;
}
_Properties.GetOrAdd(bg.source, ddp => new PropertyChangeHandler { property = ddp }); //incase it wasnt added before.
});
}
public static BindingInfoGroup GetOnewayBindings(FrameworkElement source)
=> source.GetValue(OnewayBindingsProperty) as BindingInfoGroup;
public static void SetOnewayBindings(FrameworkElement source, string value)
=> source.SetValue(OnewayBindingsProperty, value);
#endregion
}
public class PropertyChangeHandler
{
internal DependencyProperty property { get; set; }
public void callback(object obj, EventArgs args)
{
var fwe = obj as FrameworkElement;
var target = fwe.DataContext;
if (fwe == null) return;
if (target == null) return;
var bg = Bindings.GetOnewayBindings(fwe);
if (bg == null) return;
else bg.InfoList
.Where(bi => bi.isResolved)
.Where(bi => bi.source == property)
.ToList()
.ForEach(bi =>
{
//transfer data to the object
var data = fwe.GetValue(property);
KeyValuePair<object, PropertyInfo>? pinfo = resolveProperty(target, bi.targetProperty);
if (pinfo == null) return;
else pinfo.Value.Value.SetValue(pinfo.Value.Key, data);
});
}
private KeyValuePair<object, PropertyInfo>? resolveProperty(object target, string path)
{
try
{
var parts = path.Split('.');
if (parts.Length == 1) return new KeyValuePair<object, PropertyInfo>(target, target.GetType().GetProperty(parts[0]));
else //(parts.Length>1)
return resolveProperty(target.GetType().GetProperty(parts[0]).GetValue(target),
string.Join(".", parts.Skip(1)));
}
catch (Exception e) //too lazy to care :D
{
return null;
}
}
}
And to use the XAML...
<Grid ab:Bindings.OnewayBindings="IsMouseOver:mouseOver;">...</Grid>

real time loop counter ouput wpf

What can I do if I want to have a text-box representing in real time the value of a loop counter in wpf?
appending working solution:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private delegate void UpdateTextBox(DependencyProperty dp, Object value);
...
private void MyMethod()
{
...
int iMax=...;
...
MyClass iMyClass = new MyClass(arguments);
this.DataContext = iMyClass;
UpdateTextBox updateTBox = new UpdateTextBox(textBlock1.SetValue);
for (int i = 1; i <= iMax; i++)
{
iMyClass.MyClassMethod(i);
Dispatcher.Invoke(updateTBox, System.Windows.Threading.DispatcherPriority.Background, new object[] { MyClass.MyPropertyProperty, iMyClass.myProperty });
}
Here is the code I tried according to your suggestion, but it doesnt work, I get "0" written in the textbox, so I suppose the binding is OK, but the loop doesnt work. I also made the loop to write into another textbox directly by textbox2.text="a" inside the loop, but it didnt work either.
/// <summary>
/// Interaction logic for Window1.xaml
/// DOESNT WORK PROPERLY
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TestClass tTest = new TestClass();
this.DataContext = tTest ;
tTest.StartLoop();
}
}
public class TestClass : DependencyObject
{
public TestClass()
{
bwLoop = new BackgroundWorker();
bwLoop.DoWork += (sender, args) =>
{
// do your loop here -- this happens in a separate thread
for (int i = 0; i < 10000; i++)
{
LoopCounter=i;
}
};
}
BackgroundWorker bwLoop;
public int LoopCounter
{
get { return (int)GetValue(LoopCounterProperty); }
set { SetValue(LoopCounterProperty, value); }
}
public static readonly DependencyProperty LoopCounterProperty = DependencyProperty.Register("LoopCounter", typeof(int), typeof(TestClass));
public void StartLoop()
{
bwLoop.RunWorkerAsync();
}
}
}
Run the loop in a background process (so that the UI can update itself while the loop is running) and
write the loop counter into a property (DependencyProperty or CLR property with INotifyPropertyChanged) which is bound to a TextBox in your user interface. Alternatively, you can directly change the value of the TextBox via Dispatcher.Invoke (this is less elegant, though).
Does this help? Feel free to ask for clarification...
Code example (untested), using a DependencyProperty (which must be bound to a TextBox):
BackgroundWorker bwLoop;
public static readonly DependencyProperty LoopCounterProperty =
DependencyProperty.Register("LoopCounter", typeof(int),
typeof(Window1), new FrameworkPropertyMetadata(0));
public int LoopCounter {
get { return (int)this.GetValue(LoopCounterProperty); }
set { this.SetValue(LoopCounterProperty, value); }
}
private MyWindow() {
...
bwLoop = new BackgroundWorker();
bwLoop.DoWork += (sender, args) => {
...
for (int i = 0; i < someLimit; i++) {
Dispatcher.Invoke(new Action(() => LoopCounter=i));
System.Threading.Thread.Sleep(250); // do your work here
}
}
bwLoop.RunWorkerCompleted += (sender, args) => {
if (args.Error != null)
MessageBox.Show(args.Error.ToString());
};
}
private void StartLoop() {
bwLoop.RunWorkerAsync();
}
you can use Data Binding for continuously updating the text.

Resources