How to detect a change in the Text property of a TextBlock? - wpf

Is there any way to detect a change in the Text property of a TextBlock element using events?
(I'm trying to provide an animation for highlighting the TextBlocks whose Text property change within a DataGrid)

It's easier than that! Late answer, but much simpler.
// assume textBlock is your TextBlock
var dp = DependencyPropertyDescriptor.FromProperty(
TextBlock.TextProperty,
typeof(TextBlock));
dp.AddValueChanged(textBlock, (sender, args) =>
{
MessageBox.Show("text changed");
});

This is like the code from the link in bioskope's answer, but simplified. You need the TargetUpdated event and add NotifyOnTargetUpdated=True to the binding.
<TextBlock Text="{Binding YourTextProperty, NotifyOnTargetUpdated=True}"
TargetUpdated="YourTextEventHandler"/>

As far as I can understand there isn't any textchanged event in TextBlock. Looking at your requirement, I feel that re-templating a textbox will also not be a viable solution. From my preliminary searching around, this seems to be a possible solution.
<TextBlock x:Name="tbMessage" Text="{Binding Path=StatusBarText, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:0″
To="1.0″ />
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:2″
From="1.0″ To="0.0″ BeginTime="0:0:5″ />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>

Bind the Text property to a DependencyProperty, which has an event trigger:
public static string GetTextBoxText(DependencyObject obj)
{
return (string)obj.GetValue(TextBoxTextProperty);
}
public static void SetTextBoxText(DependencyObject obj, string value)
{
obj.SetValue(TextBoxTextProperty, value);
}
public static readonly DependencyProperty TextBoxTextProperty =
DependencyProperty.RegisterAttached(
"TextBoxText",
typeof(string),
typeof(TextBlockToolTipBehavior),
new FrameworkPropertyMetadata(string.Empty, TextBoxTextChangedCallback)
);
private static void TextBoxTextChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = d as TextBlock;
HandleTextChange(textBlock);
}
In the XAML Bind to the TextBlock text Property:
<TextBlock
Text="{Binding SomeProperty, UpdateSourceTrigger=PropertyChanged}"
th:TextBlockBehavior.TextBoxText="{Binding Text,
RelativeSource={RelativeSource Self}}" />

Here is a similar example on MSDN using code-behind:
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.targetupdated.aspx

Here's something you can use I picked up from Jerry Nixon and Daren May at the Microsoft Virtual Academy "Developing Universal Windows Apps with C# and XAML" and the code that contains the DependencyObject logic is here "(W8.1-WP8.1) UNIVERSAL APP FOR MVA".
namespace App1.Behaviors
{
// <summary>
/// Helper class that allows you to monitor a property corresponding to a dependency property
/// on some object for changes and have an event raised from
/// the instance of this helper that you can handle.
/// Usage: Construct an instance, passing in the object and the name of the normal .NET property that
/// wraps a DependencyProperty, then subscribe to the PropertyChanged event on this helper instance.
/// Your subscriber will be called whenever the source DependencyProperty changes.
/// </summary>
public class DependencyPropertyChangedHelper : DependencyObject
{
/// <summary>
/// Constructor for the helper.
/// </summary>
/// <param name="source">Source object that exposes the DependencyProperty you wish to monitor.</param>
/// <param name="propertyPath">The name of the property on that object that you want to monitor.</param>
public DependencyPropertyChangedHelper(DependencyObject source, string propertyPath)
{
// Set up a binding that flows changes from the source DependencyProperty through to a DP contained by this helper
Binding binding = new Binding
{
Source = source,
Path = new PropertyPath(propertyPath)
};
BindingOperations.SetBinding(this, HelperProperty, binding);
}
/// <summary>
/// Dependency property that is used to hook property change events when an internal binding causes its value to change.
/// This is only public because the DependencyProperty syntax requires it to be, do not use this property directly in your code.
/// </summary>
public static DependencyProperty HelperProperty =
DependencyProperty.Register("Helper", typeof(object), typeof(DependencyPropertyChangedHelper), new PropertyMetadata(null, OnPropertyChanged));
/// <summary>
/// Wrapper property for a helper DependencyProperty used by this class. Only public because the DependencyProperty syntax requires it.
/// DO NOT use this property directly.
/// </summary>
public object Helper
{
get { return (object)GetValue(HelperProperty); }
set { SetValue(HelperProperty, value); }
}
// When our dependency property gets set by the binding, trigger the property changed event that the user of this helper can subscribe to
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var helper = (DependencyPropertyChangedHelper)d;
helper.PropertyChanged(d, e);
}
/// <summary>
/// This event will be raised whenever the source object property changes, and carries along the before and after values
/// </summary>
public event EventHandler<DependencyPropertyChangedEventArgs> PropertyChanged = delegate { };
}
}
Usage XAML:
<TextBlock Grid.Row="0"
x:Name="WritingMenuTitle"
HorizontalAlignment="Left"
FontSize="32"
FontWeight="SemiBold"
Text="{Binding WritingMenu.Title}"
TextAlignment="Left"
TextWrapping="Wrap"/>
Usage xaml.cs:
Behaviors.DependencyPropertyChangedHelper helper = new Behaviors.DependencyPropertyChangedHelper(this.WritingMenuTitle, Models.CommonNames.TextBlockText);
helper.PropertyChanged += viewModel.OnSenarioTextBlockTextChangedEvent;
Usage viewmodel.cs:
public async void OnSenarioTextBlockTextChangedEvent(object sender, DependencyPropertyChangedEventArgs args)
{
StringBuilder sMsg = new StringBuilder();
try
{
Debug.WriteLine(String.Format(".....WritingMenuTitle : New ({0}), Old ({1})", args.NewValue, args.OldValue));
}
catch (Exception msg)
{
#region Exception
.....
#endregion
}
}

Related

How do I set the binding of a child proxy relative to the DataContext?

I looked at a lot of topics, but they all in one way or another are related to the definition of the DataContext of UI elements.
I have a task that requires a completely different approach.
And no matter how much I puzzled over the decision, I could not think of anything.
Description of the problem.
Initially, there is a simple proxy:
using System;
using System.Windows;
namespace Proxy
{
/// <summary> Provides a <see cref="DependencyObject"/> proxy with
/// one property and an event notifying about its change. </summary>
public class Proxy : Freezable
{
/// <summary> Property for setting external bindings. </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(object), typeof(Proxy), new PropertyMetadata(null));
protected override Freezable CreateInstanceCore()
{
throw new NotImplementedException();
}
}
}
If you set it in the Resources of any element, then it can get the DataContext with a simple Binding:
<FrameworkElement.Resources>
<proxy:ProxyValue x:Key="proxy"
Value="{Binding}"/>
</FrameworkElement.Resources>
Likewise, any Bindig without an explicitly specified source will use the DataContext of the element in whose resources the proxy instance is declared as the source.
Child proxy injection.
Now, for a certain task (its conditions are not relevant to the question, so I will not describe it) I needed a nested (child) proxy which can also be assigned a binding relative to the data context.
And I need to set this binding in code.
A highly simplified example for demonstration:
using System.Windows.Data;
namespace Proxy
{
public class PregnantProxy : Proxy
{
public Proxy Child { get; } = new Proxy();
public PregnantProxy()
{
Binding binding = new Binding();
BindingOperations.SetBinding(this, ValueProperty, binding);
BindingOperations.SetBinding(Child, ValueProperty, binding);
}
}
}
<StackPanel DataContext="Some data">
<FrameworkElement.Resources>
<proxy:PregnantProxy x:Key="proxy"/>
</FrameworkElement.Resources>
<TextBlock Text="{Binding}" Margin="10"/>
<TextBlock Text="{Binding Value, Source={StaticResource proxy}}" Margin="10"/>
<TextBlock Text="{Binding Child.Value, Source={StaticResource proxy}}" Margin="10"/>
</StackPanel>
Parent proxy binding works as expected.
But linking a child will not return anything.
How can you set the correct binding for a child?
"If you set it in the Resources of any element, then it can get the DataContext with a simple Binding" - this is the crucial mistake. Resource dictionary has not the DataContext inheritance. You can easy see it, if you add to the resource dictionary e.g. a Label and try to use binding for it(see example below).
That it works for Text="{Binding Value, Source={StaticResource proxy}}" lays on inheritance from Freezable class, which finds out data context and use for it if I'm not mistaken Freezable.ContextList, which is private see implementation of Freezable. This implementation doesn't work for Child, since it's not in a resource dictionary.
So if you do inherit not from Freezable, but from let us say it's parent class DependencyObject, also Text="{Binding Value, Source={StaticResource proxy}}" will not work.
I don't know for what you need this construction, it looks for me a kind of weird, but if you inherit from FrameworkElement and provide a DataContext for the proxy and it's child element (in XAML you can hard code it, or use StaticResource or custom MarkupExtension for it) it can work. See modified code.
public class Proxy : FrameworkElement
{
/// <summary> Property for setting external bindings. </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(object), typeof(Proxy), new PropertyMetadata(null));
//protected override Freezable CreateInstanceCore()
//{
// throw new NotImplementedException();
//}
}
public class PregnantProxy : Proxy
{
public Proxy Child { get; } = new Proxy();
public PregnantProxy()
{
var binding = new Binding() {};
BindingOperations.SetBinding(this, ValueProperty, binding);
//Child
this.AddLogicalChild(Child);
BindingOperations.SetBinding(Child, DataContextProperty, binding);
BindingOperations.SetBinding(Child, ValueProperty, binding);
}
}
and in XAML accordingly:
<StackPanel DataContext="Some data">
<StackPanel.Resources>
<local:PregnantProxy x:Key="proxyResBinding" DataContext="{Binding}"/>
<local:PregnantProxy x:Key="proxyHardCodedDC" DataContext="Proxy hardcoded DC"/>
<Label x:Key="lblResBinding" DataContext="{Binding}"/>
<Label x:Key="lblHardcoded" DataContext="hard coded DC"/>
</StackPanel.Resources>
<Label Content="{Binding}" Background="Yellow" />
<Label Content="{Binding Child.Value, Source={StaticResource proxyResBinding}}" Background="Red"/>
<Label Content="{Binding Value, Source={StaticResource proxyResBinding}}" Background="Red"/>
<Label Content="{Binding Child.Value, Source={StaticResource proxyHardCodedDC}}" Background="Green"/>
<Label Content="{Binding Value, Source={StaticResource proxyHardCodedDC}}" Background="Green"/>
<Label Content="{Binding DataContext, Source={StaticResource lblResBinding}}" Background="Red"/>
<Label Content="{Binding DataContext, Source={StaticResource lblHardcoded}}" Background="Green"/>
</StackPanel>
For now, I've implemented a working solution with finding the parent FrameworkElement and adding a child proxy to the resources of the parent FrameworkElement.
The test use case shown in the question works correctly.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Proxy
{
public class PregnantProxy : Proxy
{
public Proxy Child { get; } = new Proxy();
public PregnantProxy()
{
BindingOperations.SetBinding(this, ParentProperty, FindAncestorFrameworkElement);
Binding binding = new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(StackPanel), 1) };
BindingOperations.SetBinding(this, ValueProperty, binding);
BindingOperations.SetBinding(Child, ValueProperty, binding);
}
/// <summary>
/// Родительский FrameworkElement
/// </summary>
public FrameworkElement Parent
{
get { return (FrameworkElement)GetValue(ParentProperty); }
set { SetValue(ParentProperty, value); }
}
/// <summary><see cref="DependencyProperty"/> для свойства <see cref="Parent"/>.</summary>
public static readonly DependencyProperty ParentProperty =
DependencyProperty.Register(nameof(Parent), typeof(FrameworkElement), typeof(PregnantProxy), new PropertyMetadata(null, ParentChanged));
private static void ParentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PregnantProxy proxy = (PregnantProxy)d;
if (e.OldValue is FrameworkElement oldElement)
{
oldElement.Resources.Remove(proxy.key);
}
if (e.NewValue is FrameworkElement newElement)
{
double key;
do
{
key = random.NextDouble();
} while (newElement.Resources.Contains(key));
newElement.Resources.Add(proxy.key = key, proxy.Child);
}
if (!Equals(BindingOperations.GetBinding(proxy, ParentProperty), FindAncestorFrameworkElement))
BindingOperations.SetBinding(proxy, ParentProperty, FindAncestorFrameworkElement);
}
private double key;
private static readonly Random random = new Random();
private static readonly Binding FindAncestorFrameworkElement = new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(FrameworkElement), 1) };
}
}
But such a solution may throw an exception if the resources are locked or read-only.
The final solution.
The answer from #Rekshino pushed me to look for a solution in a different direction.
I created an extension method to set a DependencyObject's context relative to another DependencyObject.
The method can be applied to any DependecyObject.
But context-relative bindings are only interpreted by Freezable.
So it makes little sense for the rest of the DependecyObject.
But maybe I missed something, and I can somehow use it or modify it.
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
namespace Proxy
{
public static class ProxyExtensionMethods
{
private static readonly Func<DependencyObject, DependencyObject, DependencyProperty, bool> ProvideSelfAsInheritanceContextHandler;
static ProxyExtensionMethods()
{
var methods = typeof(DependencyObject)
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo method = null;
foreach (var meth in methods
.Where(m => m.Name == "ProvideSelfAsInheritanceContext" &&
m.ReturnType == typeof(bool)))
{
var parameters = meth.GetParameters();
if (parameters?.Length == 2 &&
typeof(DependencyObject) == parameters[0].ParameterType &&
typeof(DependencyProperty) == parameters[1].ParameterType)
{
method = meth;
break;
}
}
ProvideSelfAsInheritanceContextHandler = (Func<DependencyObject, DependencyObject, DependencyProperty, bool>)
method
.CreateDelegate
(
typeof(Func<DependencyObject, DependencyObject, DependencyProperty, bool>)
);
}
/// <summary>Sets the DependecyObject context</summary>
/// <param name="obj">The object for which the Context is to be set.</param>
/// <param name="context">The object to be used as the Context.</param>
public static void SetContext(this DependencyObject obj, DependencyObject context)
{
ProvideSelfAsInheritanceContextHandler(context, obj, PrivateKey.DependencyProperty);
}
private static readonly DependencyPropertyKey PrivateKey=
DependencyProperty.RegisterAttachedReadOnly("Private", typeof(object), typeof(ProxyExtensionMethods), new PropertyMetadata(null));
}
}
Usage example.
using System.Windows.Data;
namespace Proxy
{
public class PregnantProxy : Proxy
{
public Proxy Child { get; } = new Proxy();
public PregnantProxy()
{
Child.SetContext(this);
Binding binding = new Binding() { };
BindingOperations.SetBinding(this, ValueProperty, binding);
BindingOperations.SetBinding(Child, ValueProperty, binding);
}
}
}
The XAML shown in the question works correctly with such an implementation.
If someone has comments on the code and possible problems with such an implementation, I am ready to listen carefully.

Altering border's corner radius on WPF Expander control?

Very high up the visual tree in WPF's Expander control is a border element (see screenshot). By default this has a CornerRadius of 3. Is it possible to modify this value?
I'll leave marking as answer for now but I managed to implement the solution as follows:
Using stylesnooper I obtained the style / control template used for the 'standard' Expander control.
Then after discovering it didn't quite behave as expected, figured out that the line <ToggleButton IsChecked="False" ... is wrong and should actually be <ToggleButton IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"...
Everything then worked as expected.
I made a behavior which modifies the first found border in the ControlTemplate. You can easily extend the behavior with new properties where u want to modify
/// <summary>
/// modifies the first found <see cref="Border"/> in the <see cref="ControlTemplate"/> of the attached <see cref="Control"/>
/// </summary>
public class ModifyBorderBehavior : Behavior<Control>
{
// ##############################################################################################################################
// Properties
// ##############################################################################################################################
#region Properties
/// <summary>
/// The new corner radius
/// </summary>
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
/// <summary>
/// The <see cref="CornerRadius"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(ModifyBorderBehavior));
#endregion
// ##############################################################################################################################
// Constructor
// ##############################################################################################################################
#region Constructor
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += _OnLoaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
//var children = VisualTree.GetVisualChildCollection<Border>(sender);
if (sender is Control control)
{
Border border = VisualTree.GetVisualChild<Border>(control);
if(ReadLocalValue(CornerRadiusProperty) != DependencyProperty.UnsetValue)
border.CornerRadius = CornerRadius;
}
}
#endregion
}
<Expander>
<i:Interaction.Behaviors>
<zls:ModifyBorderBehavior CornerRadius="0"/>
</i:Interaction.Behaviors>
</Expander>

How to disable the WPF ScrollViewer to refresh the view

I am using a stack panel for displaying a slideshow. The stack panel contains two images.
Each image is bound to the View Model containing among other properties the path to the image on the disk. In the general case there are more than 2 images in the list.
There is an endless while loop used for changing the current and next pointer to the image and in this way after the slide transition is finished the the current image becomes previous and the next becomes current.
The slide transition is made by changing the scroll viewer from 0 to 100% of scrollable extended area.
This works and even very fast. It is the easiest way to implement a slide transition for an endless list of images using MVVM pattern.
My problem is that from time to time the slide transition slips and doesn't finishes at exact 100% visually but the storyboard animation completed event is triggered.
In the ViewModel I am moving the Current and Next poitners and the image blinks. Some flickering is visible from time to time.
I need to find a way on how to temporarily disable the ScrollViewer to display any visual change when I am repositioning the scoller from 100% to 0%.
I need to invalidate the WPF ScrollViewer only temporarily until I change the scroll position from 100% to 0% but this must not be visible on screen.
I mean the values must be changed but the screen must be frozen.
Is it possible somehow ?
XAML
<UserControl.Resources>
<Storyboard x:Key="sbVerticalSlideEasingTransition"
AutoReverse="True" RepeatBehavior="Forever">
<Storyboard x:Key="sbVerticalSlideTransition"
FillBehavior="HoldEnd">
<!--SCROLL TO THE MULTIPLIER OF THE HEIGHT (BASICALLY AS THE PERCENTAGE)-->
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="Mediator"
Storyboard.TargetProperty="ScrollableHeightMultiplier">
<LinearDoubleKeyFrame KeyTime="00:0:0" Value="0" />
<LinearDoubleKeyFrame KeyTime="00:0:5" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="gdContainer" Background="Black">
<ScrollViewer x:Name="Scroller" Visibility="{Binding Path=IsSlider, Mode=TwoWay, NotifyOnSourceUpdated=True, ElementName=slidePlayer,
Converter='{StaticResource BoolToVisibility}'}"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Disabled">
<StackPanel x:Name="ScrollPanel">
<ctrl:MediaElementEx x:Name="slide1" Height="{Binding Path=ActualHeight, ElementName=gdContainer}" Width="{Binding Path=ActualWidth, ElementName=gdContainer}" />
<ctrl:MediaElementEx x:Name="slide2" Height="{Binding Path=ActualHeight, ElementName=gdContainer}" Width="{Binding Path=ActualWidth, ElementName=gdContainer}" />
</StackPanel>
</ScrollViewer>
<!-- Mediator that forwards the property changes -->
<cmn:ScrollViewerOffsetMediator x:Name="Mediator" ScrollViewer="{Binding ElementName=Scroller}"/>
</Grid>
ScrollViewer Mediator
This is used to bind the ScrollOffset of the scrollviewer that is not bindable directly
/// <summary>
/// Mediator that forwards Offset property changes on to a ScrollViewer
/// instance to enable the animation of Horizontal/VerticalOffset.
/// </summary>
public class ScrollViewerOffsetMediator : FrameworkElement
{
/// <summary>
/// ScrollViewer instance to forward Offset changes on to.
/// </summary>
public ScrollViewer ScrollViewer
{
get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
set { SetValue(ScrollViewerProperty, value); }
}
public static readonly DependencyProperty ScrollViewerProperty =
DependencyProperty.Register(
"ScrollViewer",
typeof(ScrollViewer),
typeof(ScrollViewerOffsetMediator),
new PropertyMetadata(OnScrollViewerChanged));
private static void OnScrollViewerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var mediator = (ScrollViewerOffsetMediator)o;
var scrollViewer = (ScrollViewer)(e.NewValue);
if (null != scrollViewer)
{
scrollViewer.ScrollToVerticalOffset(mediator.VerticalOffset);
}
}
/// <summary>
/// VerticalOffset property to forward to the ScrollViewer.
/// </summary>
public double VerticalOffset
{
get { return (double)GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
}
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.Register(
"VerticalOffset",
typeof(double),
typeof(ScrollViewerOffsetMediator),
new PropertyMetadata(OnVerticalOffsetChanged));
public static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var mediator = (ScrollViewerOffsetMediator)o;
if (null != mediator.ScrollViewer)
{
mediator.ScrollViewer.ScrollToVerticalOffset((double)(e.NewValue));
}
}
/// <summary>
/// HorizontalOffset property to forward to the ScrollViewer.
/// </summary>
public double HorizontalOffset
{
get { return (double)GetValue(HorizontalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
}
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.Register(
"HorizontalOffset",
typeof(double),
typeof(ScrollViewerOffsetMediator),
new PropertyMetadata(OnHorizontalOffsetChanged));
public static void OnHorizontalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var mediator = (ScrollViewerOffsetMediator)o;
if (null != mediator.ScrollViewer)
{
mediator.ScrollViewer.ScrollToHorizontalOffset((double)(e.NewValue));
}
}
/// <summary>
/// Multiplier for ScrollableHeight property to forward to the ScrollViewer.
/// </summary>
/// <remarks>
/// 0.0 means "scrolled to top"; 1.0 means "scrolled to bottom".
/// </remarks>
public double ScrollableHeightMultiplier
{
get { return (double)GetValue(ScrollableHeightMultiplierProperty); }
set { SetValue(ScrollableHeightMultiplierProperty, value); }
}
public static readonly DependencyProperty ScrollableHeightMultiplierProperty =
DependencyProperty.Register(
"ScrollableHeightMultiplier",
typeof(double),
typeof(ScrollViewerOffsetMediator),
new PropertyMetadata(OnScrollableHeightMultiplierChanged));
public static void OnScrollableHeightMultiplierChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var mediator = (ScrollViewerOffsetMediator)o;
var scrollViewer = mediator.ScrollViewer;
if (null != scrollViewer)
{
scrollViewer.ScrollToVerticalOffset((double)(e.NewValue) * scrollViewer.ScrollableHeight);
if (((double)e.NewValue == 0) && ((double)e.OldValue == 1))
{
Debug.WriteLine("[1] {0:HH:mm:ss.fff} ScrollableHeightMultiplier 1 => 0 ", DateTime.Now);
}
}
}
/// <summary>
/// Multiplier for ScrollableWidth property to forward to the ScrollViewer.
/// </summary>
/// <remarks>
/// 0.0 means "scrolled to top"; 1.0 means "scrolled to bottom".
/// </remarks>
public double ScrollableWidthMultiplier
{
get { return (double)GetValue(ScrollableWidthMultiplierProperty); }
set { SetValue(ScrollableWidthMultiplierProperty, value); }
}
public static readonly DependencyProperty ScrollableWidthMultiplierProperty =
DependencyProperty.Register(
"ScrollableWidthMultiplier",
typeof(double),
typeof(ScrollViewerOffsetMediator),
new PropertyMetadata(OnScrollableWidthMultiplierChanged));
public static void OnScrollableWidthMultiplierChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var mediator = (ScrollViewerOffsetMediator)o;
var scrollViewer = mediator.ScrollViewer;
if (null != scrollViewer)
{
scrollViewer.ScrollToHorizontalOffset((double)(e.NewValue) * scrollViewer.ScrollableWidth);
}
}
}

What is the best way to diagnose WPF UserControl DependencyProperty issues?

Here is my issue, I created a UserControl as follows:
XAML:
<UserControl x:Class="ProcessVisualizationBar.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" xmlns:lb="clr-namespace:ProcessVisualizationBar"
Name="ProcessVisualizationBar">
<Border BorderBrush="Silver" BorderThickness="1,1,1,1" Margin="0,5,5,5" CornerRadius="5" Padding="2">
<ListBox Name="ProcessVisualizationRibbon" Grid.Column="1" Height="40" ItemsSource="{Binding ElementName=ProcessVisualizationBar, Path=ItemsSource}"/>
</Border>
</UserControl>
Code Behind(C#):
using System.Windows;
using System.Windows.Controls;
namespace ProcessVisualizationBar
{
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IEnumerable), typeof(UserControl));
public System.Collections.IEnumerable ItemsSource
{
get { return ProcessVisualizationRibbon.ItemsSource; }
set { ProcessVisualizationRibbon.ItemsSource = value; }
}
public UserControl1()
{
InitializeComponent();
}
}
}
I build my Usercontrol and add the .dll to the reference of another project. I add the reference at the top of my XAML as such:
xmlns:uc="clr-namespace:ProcessVisualizationBar;assembly=ProcessVisualizationBar"
Then I go to use the control.
<uc:UserControl1 Grid.Row="2" x:Name="ProcessVisualizationContent" />
It finds the control okay, but when I try and find the ItemsSource Property I added to it, I'm not finding it. I'm not sure what I missed, and I'm not sure what debug tools are really available to figure this out.
Anyone have some experience with this that can share their wisdom?
What is the actual data being passed? That is what you should be creating and not a pass through situation which you are attempting.
Create a dependency property targetting the actual data to be passed with a property changed handler. On the change event, then call internal code to bind it to the ProcessVisualazation ItemsSource. That way you can debug when the data comes through by placing a breakpoint in the event.
Here is an example where the consumer will see StringData in the Xaml and needs to pas a list of strings into the custom control:
#region public List<string> StringData
/// <summary>
/// This data is to be bound to the ribbon control
/// </summary>
public List<string> StringData
{
get { return GetValue( StringDataProperty ) as List<string>; }
set { SetValue( StringDataProperty, value ); }
}
/// <summary>
/// Identifies the StringData dependency property.
/// </summary>
public static readonly System.Windows.DependencyProperty StringDataProperty =
System.Windows.DependencyProperty.Register(
"StringData",
typeof( List<string> ),
typeof( UserControl ),
new System.Windows.PropertyMetadata( null, OnStringDataPropertyChanged ) );
/// <summary>
/// StringDataProperty property changed handler.
/// </summary>
/// <param name="d">DASTreeBinder that changed its StringData.</param>
/// <param name="e">Event arguments.</param>
private static void OnStringDataPropertyChanged( System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e )
{
UserControl source = d as UserControl;
List<string> value = e.NewValue as List<string>;
BindDataToRibbon( value );
}
#endregion public List<string> StringData
Now just create a BindDataToRibbon method which will do the dirty work. Note that I use Jeff Wilcox's Silverlight dependency snippets in Visual Studio to generate the above dependency. I have used it for WPF and Silverlight projects.

WPF ItemsControl Binding to UserControl

I have a WPF application where I need to do something like that :
<ItemsControl x:Name="lstProducts">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProductName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I set the ItemsSource in code like this : lstProducts.ItemsSource = MyEFContext.Products;
Up to now everything is working fine. Now I want to use my own UserControl to display a product instead of the TextBlock like that.
<ItemsControl x:Name="lstProducts">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:ProductItemCtl ProductName="{Binding ProductName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my userControl, I created a DependencyProperty like that (see below) where I set the ProductName in the OnProductNameChanged callback.
The TextBlock in my usercontrol is not updated when binding the ItemsControl, and the callback is not launched.
#region ProductName
/// <summary>
/// ProductName Dependency Property
/// </summary>
public static readonly DependencyProperty ProductNameProperty =
DependencyProperty.Register("ProductName", typeof(String), typeof(ProductItemCtl),
new FrameworkPropertyMetadata("",
new PropertyChangedCallback(OnProductNameChanged)));
/// <summary>
/// Gets or sets the ProductName property. This dependency property
/// indicates ....
/// </summary>
public String ProductName
{
get { return (String)GetValue(ProductNameProperty); }
set { SetValue(ProductNameProperty, value); }
}
/// <summary>
/// Handles changes to the ProductName property.
/// </summary>
private static void OnProductNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProductItemCtl target = (ProductItemCtl)d;
String oldProductName = (String)e.OldValue;
String newProductName = target.ProductName;
target.OnProductNameChanged(oldProductName, newProductName);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the ProductName property.
/// </summary>
protected virtual void OnProductNameChanged(String oldProductName, String newProductName)
{
// Set Product Name in the display Here!
this.txtProductName.Text = newProductName;
}
#endregion
Not an answer, but i can't write this as comment. What happens if you simplify your code like this:
public static readonly DependencyProperty ProductNameProperty = DependencyProperty.Register(
"ProductName", typeof(string), typeof(ProductItemCtl),
new FrameworkPropertyMetadata(string.Empty,
(o, e) => ((ProductItemCtl)o).txtProductName.Text = (string)e.NewValue));
Arrrghhh got it... I had this.ProductName = ""; in the constructor of my UserControl
I wasted so much time on this... And sorry for wasting all of yours.
Since I found out by trying your code Clemens I will mark your answer as "Answered".
thanks everyone.

Resources